Enumeration Classes and WCF

During the last week I was working on a real fun project. It started after I stumbled across an old article by Jimmy Bogard, author of the famous AutoMapper library. In his post he talks about Enumeration Classes. What they are, how they work and what their advantages over simple enums are.

He started by stating that a certain question evolved from a discussion on ALT.NET: “What do you do about enumerations crossing service boundaries?”. His post is inspiring but he did not really give an answer to that exact question.

Although WCF is by no means the only way to implement “services” this unanswered question got me thinking: “What if I want to use functionality-enhanced enumeration classes in a WCF service and simple enums on the client side?”

I know that WCF was designed to “talk dirty” when it comes to conversion between client- and server-side objects. It doesn’t matter if you have classes on one and structs on the other side. They don’t have to have the same (number of) fields. Using the DataMemberAttribute you can name the fields differently or even forward information a certain client does not use in its contracts further down the stream when that client calls another service by implementing IExtensibleDataObject in your objects.

But can it talk enums on the client and enumeration classes on the server? As it turns out: Yes it can!

The SortOrder Enumeration Class

For the following sample I will use a modified version of the SortOrder enumeration class I grabbed from the Tarantino project Jimmy mentions in his article. One of the changes I made to the enumeration base class is to introduce a Name property in addition to the DisplayName. That allows you to handle enumeration classes in a very similar way to real enums. I also added non-generic overloads for the FromValue and FromName methods. This is the modified SortOrder enumeration class.

[Serializable]
public class SortOrder : Enumeration
{
  public static readonly SortOrder Ascending = new AscendingSortOrder();
  public static readonly SortOrder Descending = new DescendingSortOrder();
  private readonly IComparer comparer;
  public SortOrder()
  {
  }
  public SortOrder(int value, string displayName, string name, IComparer comparer)
    : base(value, displayName, name)
  {
    this.comparer = comparer;
  }
  public IComparer Comparer
  {
    get { return comparer; }
  }
  private class AscendingSortOrder : SortOrder
  {
    public AscendingSortOrder()
      : base(0, "Sort ascending", "Ascending", StringComparer.OrdinalIgnoreCase)
    {
    }
  }
  private class DescendingSortOrder : SortOrder
  {
    public DescendingSortOrder()
      : base(1, "Sort descending", "Descending", new DescendingStringComparer())
    {
    }
    private class DescendingStringComparer : IComparer
    {
      public int Compare(string x, string y)
      {
        return -StringComparer.OrdinalIgnoreCase.Compare(x, y);
      }
    }
  }
}

Control (De-)Serialization with IDataContractSurrogate

The task is not quite as easy as I thought at first. The WCF infrastructure gives us the IDataContractSurrogate interface. By implementing this interface you can control how WCF serializes and deserializes specific objects.

Serialization is easy. When WCF calls the GetDataContractType method you return typeof(string) and in your implementation of the GetObjectToSerialize method you return the value of the Name property of your enumeration class and the resulting XML looks exactly as if you were serializing a simple enum. But the real deal is the deserialization. WCF matches the incoming serialized enum to the enumeration class. So far so good. But WCF is not able to convert the serialized input value to the matching enumeration class. If you try to use that enumeration type directly without modifying it on the call to GetDataContractTye WCF will fail with an exception. It can’t do the deserialization with the information it has. But it won’t call GetDeserializedObject for help either. If you return typeof(string) WCF will fail as well telling you it can’t cast a string to your enumeration class type. Even if you implement an implicit or explicit cast operator from string to your enumeration you will get that exception. And it also won’t call GetDeserializedObject. A somewhat strange behavior but there you are.

But I was not willing to give up. If WCF can’t handle primitive values like strings and can’t handle your enumeration class directly but it can handle enums, why not try to treat the incoming value as a custom enum and convert that enum to your enumeration class? I know that sounds stupid. You don’t want to write and maintain code for an enumeration class and an enum on the server side. But you don’t have to. Some prior experiments with emitting IL code taught me that it is very easy to generate such an enum on-the-fly.

Reflection.Emit to the Rescue

So what I did is I created an IL generator that checks wether it already emitted an enum type for a specific enumeration class. If not it dynamically creates an enum type that has the same name as the enumeration class plus a short suffix. It also creates an enum member for every static field in your enumeration class (like SortOrder.Ascending) with the name of the field and the value of the enumeration class, decorates type and members with the DataContract- and EnumMemberAttributes and remembers that type for future use.

For SortOrder the generator creates the following enum:

[DataContract(Name="SortOrder")]
public enum SortOrder_Enum
{
  [EnumMember]
  Ascending = 0,
  [EnumMember]
  Descending = 1
}

See no Evil…

And all of a sudden everything works like a charm. The modified surrogate returns the enum type when asked for the DataContractType and you can handle conversion from enum to enumeration class by using the non-generic methods Enumeration.FromValue(Type, int) or Enumeration.FromName(Type, string) in the call to GetDeserializedObject.

public class EnumerationClassesSurrogate : IDataContractSurrogate
{
  private readonly EnumGenerator generator;
  public EnumerationClassesSurrogate()
  {
    this.generator = new EnumGenerator();
  }
  public Type GetDataContractType(Type type)
  {
    if (typeof(Enumeration).IsAssignableFrom(type))
    {
      Type enumType;
      if (this.generator.TryGetEnumByType(type, out enumType))
      {
        return enumType;
      }
    }
    return type;
  }
  public object GetObjectToSerialize(object obj, Type targetType)
  {
    Enumeration enumeration = obj as Enumeration;
    if (enumeration != null)
    {
      Type enumType;
      if (this.generator.TryGetEnumByType(obj.GetType(), out enumType) &&
          Enum.IsDefined(enumType, enumeration.Name))
      {
        return Enum.Parse(enumType, enumeration.Name);
      }
    }
    return obj;
  }
  public object GetDeserializedObject(object obj, Type targetType)
  {
    Type objType = obj.GetType();
    if (this.generator.IsGeneratedEnum(objType) &&
        typeof(Enumeration).IsAssignableFrom(targetType))
    {
      string name = Enum.GetName(objType, obj);
      return Enumeration.FromName(targetType, name);
    }
    return obj;
  }

  // other methods are not implemented for this sample
}

Serialization works in the same way. You map the enumeration class type to the generated enum type and convert the enumeration value to the matching enum value. All of this is completely transparent for client and server code.

[TestMethod]
public void Should_TransparentlyConvert_BetweenSimpleEnumOnClient_AndComplexEnumClassOnServer()
{
  IEnumerable sms = new[]
    {
      new SerializeMe2 { Text = "1" },
      new SerializeMe2 { Text = "3" },
      new SerializeMe2 { Text = "2" }
    };
  using (ServiceHost host = new ServiceHost(typeof(SortingService), new Uri("http://localhost:12345/svc")))
  {
    host.Open();
    var factory = new ChannelFactory(new BasicHttpBinding());
    var proxy = factory.CreateChannel(new EndpointAddress("http://localhost:12345/svc"));
    var result = proxy.Sort(sms, SortOrderEnum.Ascending).ToList();
    Assert.AreEqual("1", result[0].Text);
    Assert.AreEqual("2", result[1].Text);
    Assert.AreEqual("3", result[2].Text);
  }
}

[ServiceContract]
public interface ISortingService
{
  [OperationContract]
  IEnumerable Sort(IEnumerable itemsToSort, SortOrder sortOrder);
}
[ServiceContract(Name = "ISortingService")]
public interface ISortingServiceClient
{
  [OperationContract]
  IEnumerable Sort(IEnumerable itemsToSort, SortOrderEnum sortOrder);
}
[PutDataContractSurrogateBehavior]
public class SortingService : ISortingService
{
  public IEnumerable Sort(IEnumerable itemsToSort, SortOrder sortOrder)
  {
    return itemsToSort.OrderBy(s => s.Text, sortOrder.Comparer);
  }
}
[DataContract(Name = "SortOrder")]
public enum SortOrderEnum
{
  [EnumMember]
  Ascending,
  [EnumMember]
  Descending
}

What you get

Maybe you don’t consider sorting to be a worldshaking example to demonstrate the advantages of enumeration classes. But I believe that, within the boundaries of a HelloWorld! sample, it shows you quite nicely what this pattern has to offer. Enumeration classes are yet another tool in your toolbox. Use them when they seem to fit your requirements.

The posiblity to use simple enums in your clients while you reserve the much more powerful enumeration classes for the server side helps you to decouple client and server and reduce the complexity of your code. See how simple it is to sort values based on the additional information stored in your enumeration classes. The client does not have to know about that. And the server does not need to know that the client doesn’t know…

The complete source code is available here (project TecX.EnumClasses and the test suite that puts them to use can be found in TecX.EnumClasses.Test). There is a separate solution file TecX.EnumClasses as well.

About these ads

7 Responses to Enumeration Classes and WCF

  1. Pingback: Enumeration Classes as Flags « Outlawtrail – .NET Development

  2. Pingback: Enumeration Classes, WCF and Service Metadata « Outlawtrail – .NET Development

  3. Pingback: Using Expressions in WCF | Outlawtrail - .NET Development

  4. Jared says:

    Im still a little confused. Would the client contain an enum field or something after using this methodology?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: