locked
wcf enum backward compatibility RRS feed

  • Question

  • I had previously asked a question about how to support backward compatibility for enums in wcf by allowing additional enum values to be added in future versions.   The problem is summed up as..

    1. wcf serializes the member name as a string for enums

    2. on deserialization, the datacontractserializer throws an exception if that string name is not a member of the enum

    3. enums are sealed so cannot override serialization/deserialization

     

    this is a real problem and maintenance nightmare for apps that add additional options between releases.   constants are not exported to wsdl so enums are the logical choice.

    I can't understand why wcf does not support an enum attribute that tells the deserializer to default to a value.

     

    the suggested fix was to implement an IDispatchMessageFormatter...

    During the deserialization in wcf, if enum string value don't exist in predefined enum types, it will throw SerializationException. To solve this isuue, one possible way is to intercept the deserialization process. we could use IDispatchMessageFormatter/IClientMessageFormatter interface, deserialize enum value from message manually, for example, set unexpected enum string to predefined Unknown value.

    the problem with this is...

    1. every service host would need to add this to every operation

    2. how would this handle enums inside datacontracts as opposed to operation parameters

    3.  i can't see a way to  override deserialization of just enums and not every member

     

    I tried to implement a DataContract surrogate which checks for .IsEnum and tried to deserialize as a string.  however the GetDeserializedObject does not get called because string is a known type...so i tried a DataContract with one string member but this did not work either.

    I guess my questions would be...

    1.  is there any plan to add a simple attribute to enable a default enum value to the deserialization? .net 4.0?

    2. is it possible to create a custom DataContractSerializer to use on the server side and override the deserialization of enums to support backward compatibility?

    Thanks.


    Monday, June 14, 2010 7:09 PM

Answers

  • Hi JSojourn,

    that's a nice solution, thanks for sharing it!

    About your previous post: We too can't share the server's contract assembly with the client, so the client will see the auto-generated "code-less" entity classes only. That's not an issue though, since we added the enum as ServiceKnownType so that the client can use it's values to fill the int-parameter like this

     

    // client code
    car.Color = (int)Colors.Red;

     

    Cheers,

    Markus

    • Marked as answer by Wencui Qian Tuesday, June 22, 2010 8:45 AM
    Friday, June 18, 2010 4:24 PM
  • Not sure, if it's appropriate, but you could have a data contract (say, Color) encapsulating Red-Green-Blue color components, and use that type to communicate all the colors you want.

    I mean, is an enumeration even approptiate for carrying this kind of information? -- there are many flavors of red, green and blue out there, and your enumeration will be constantly changing. A type describing all possible colors, on the other hand, would give you a certain degree of flexibility.

    That said, I agree that enums support in WCF is somewhat wacky and vote for JSojourn's post above.


    Please mark this post as helpful if it is (or even mark it as an answer, if appropriate).
    • Marked as answer by Wencui Qian Tuesday, June 22, 2010 8:45 AM
    Saturday, June 19, 2010 7:59 AM

All replies

  • Hi JSojourn,

    I'm not very happy with the handling of enums in WCF  either but this is what we came up with in our current project:

    Suppose we have the following contract:

    [ServiceContract]
    public interface IAutomobiles
    {
     [OperationContract]
     Car GetCar(string make);
    }
    
    [DataContract]
    public class Car
    {
     [DataMember]
     public string Make{get;set;}
    
     [DataMember]
     public Colors Color{get;set;}
    
     [DataMember]
     public decimal Price{get;set;}
    }
    
    [DataContract]
    public enum Colors
    {
     [EnumMember]
     Blue,
     [EnumMember]
     Green,
     [EnumMember]
     Red
    }
    
    This contract would have exactly that kind of problem you described as soon as we wanted to sell yellow cars, right?

     

    That's why we changed the contract as follows:

    [ServiceContract]
    public interface IAutomobiles
    {
     [OperationContract]
     Car GetCar(string make);
    }
    
    [DataContract]
    public class Car
    {
     [DataMember]
     public string Make{get;set;}
    
     [DataMember(Name="Color")]
     public int ColorIntValue{get;set;}
    
     public Colors Color
     {
       get {return (Colors) this.ColorIntValue;}
       set {this.ColorIntValue = (int) value;}
     }
    
     [DataMember]
     public decimal Price{get;set;}
    }
    
    [DataContract]
    public enum Colors
    {
     [EnumMember]
     Blue = 1,
     [EnumMember]
     Green = 2,
     [EnumMember]
     Red = 3}
    

    Note that the enum property has no [DataContact] attribute attached to it. Instead, the int property is send over the wire (renamed to the original enum property name).

    Server code that references the contract directly can use the enum, but only the int is serialized which saves you from the dreaded deserializer exception after the enum value "Yellow" is added to the enum later on. If you want to make the enum available to the client as well, refer to this post: http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/053c79e2-beaa-4f94-8748-4ffbfaaf85e3

    Of course this works with a string property (using ToString() and Enum.Parse()) as well, if you prefer to send a string instead of the int.

     

    Hope that helps,

    Markus

    • Proposed as answer by Markus Geiger Monday, June 14, 2010 8:57 PM
    Monday, June 14, 2010 8:55 PM
  • Thanks for the reply...I guess we will have to fake the enum use like this...seems ridiculous though.
    Wednesday, June 16, 2010 3:06 PM
  • Well I just realized this won't work for us because we can't require any client-side code.  it has to be data only objects.

     

    This needs to be fixed asap.

     

    Wednesday, June 16, 2010 8:05 PM
  • by the way if you can use client-side code...i would do something like this...

     

        [DataContract]

        public struct WebEnum<T>

        {

            public WebEnum(T value) : this()

            {

                Value = value;

            }

            public T Value

            {

                get;

                set;

            }

            /////////////////////

     

            [DataMember]

            internal string Name

            {

                get;

                set;

            }

            public static implicit operator WebEnum<T>(T value)

            {

                return new WebEnum<T>(value);

            }

            public static implicit operator T(WebEnum<T> value)

            {

                return value.Value;

            }

            [OnSerializing]

            internal void OnSerializing(StreamingContext context)

            {

                Name = Value.ToString();

            }

            [OnDeserialized]

            internal void OnDeserialized(StreamingContext context)

            {

                if ((!string.IsNullOrEmpty(Name)) && (Enum.IsDefined(typeof(T), Name)))

                {

                    Value = (T)Enum.Parse(typeof(T), Name);

                }

                else

                {

                    Value = default(T);

                }

            }

        }


    Then you just have your object like...

    [DataContract]
    public class Car
    {
     [DataMember]
     public string Make{get;set;}
    
     [DataMember(Name="Color")]
     public WebEnum<Colors>  Color {get;set;}
    }

    and you can use it just like an enum...

    car.Color = Colors.Red;
    Colors color = car.Color;

    and it should be backward compatible.

    
    
    i'm thinking for us since we can't have client side code...i might try implementing a new serializer that contains a DataContractSerializer and just forwards all the calls except when its an enum...i'm not sure how much work this will be...

    does anyone have any examples of making your own serializer and where we would need to override the enum derserialization if its even possible?

    Thanks.

    • Proposed as answer by agnostic Wednesday, September 25, 2013 12:47 PM
    Friday, June 18, 2010 3:53 PM
  • Hi JSojourn,

    that's a nice solution, thanks for sharing it!

    About your previous post: We too can't share the server's contract assembly with the client, so the client will see the auto-generated "code-less" entity classes only. That's not an issue though, since we added the enum as ServiceKnownType so that the client can use it's values to fill the int-parameter like this

     

    // client code
    car.Color = (int)Colors.Red;

     

    Cheers,

    Markus

    • Marked as answer by Wencui Qian Tuesday, June 22, 2010 8:45 AM
    Friday, June 18, 2010 4:24 PM
  • Not sure, if it's appropriate, but you could have a data contract (say, Color) encapsulating Red-Green-Blue color components, and use that type to communicate all the colors you want.

    I mean, is an enumeration even approptiate for carrying this kind of information? -- there are many flavors of red, green and blue out there, and your enumeration will be constantly changing. A type describing all possible colors, on the other hand, would give you a certain degree of flexibility.

    That said, I agree that enums support in WCF is somewhat wacky and vote for JSojourn's post above.


    Please mark this post as helpful if it is (or even mark it as an answer, if appropriate).
    • Marked as answer by Wencui Qian Tuesday, June 22, 2010 8:45 AM
    Saturday, June 19, 2010 7:59 AM
  • Why was this marked as answered?

     

    My questions were...

     

    1.  is there any plan to add a simple attribute to enable a default enum value to the deserialization?

    2. is it possible to create a custom DataContractSerializer to use on the server side and override the deserialization of enums to support backward compatibility?

     

    The only thing that was answered was that there are a bunch of hacks to get this to work.  So the answer is to tell our entire team to rewrite their interfaces and objects to not use enums but send the int value?

    why is there no plan to fix the datacontractserializer to allow for backward compatibility?  isn't extensibility one of the goals for wcf (i.e. IExtensibleObject for forward compatibility)?

    what is the objection to a simple attribute on the enum to specify a default for unknown values?  or a setting on the datacontractserializer class to set unknown enums to the default(myenum).  or an example of how to create a custom datacontractserializer that does this?


     

    Tuesday, June 22, 2010 6:27 PM
  • I agree, my post cannot be marked as an answer. I hoped to contribute something helpful. Of course, changing the contract might not be an option and often isn't.

    Guys, please unmark my post, it's not an answer.

    Wednesday, June 23, 2010 7:38 PM
  • 1) There is no automatic support for specifying an default enum value in .NET Framework 4.0.  As for whether there will be one in the future, I do not know. As a rule we can't speculate about features of future versions of any product.

    2) You can specify a custom Data Contract Serializer. There is a blog post that discusses this here: http://blogs.msdn.com/b/sowmy/archive/2006/03/26/561188.aspx The post discusses preserving object references, but it talks about how to specify a custom data contract serializer by deriving a class from DataContractSerializerOperationBehavior and overriding the CreateSerializer method. 

    There is also some information about creating a data contract surrogate which would allow you to convert an enum into another type prior to serializing. You can find more information here: http://msdn.microsoft.com/en-us/library/ms733064.aspx, and sample code here: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.idatacontractsurrogate.aspx

    I hope this information is helpful,

    Michael Green

    Thursday, June 24, 2010 5:27 PM
  • Thanks Mike for the reply...

     

    1) I have successfully added a custom data contract serializer to our server.  However is there a way to derive or contain the existing DataContractSerializer and simple forward the calls to it except when deserializing an enum?  Is there any info on how to only replace the deserialization for a specific type (enum)?

     

    2) I did try and create a surrogate for enums which was successful, however I could not get it to deserialize without throwing an exception.  I could not use the basic type "string" to deserialize as the datacontractserializer ignores the surrogate for known types.  I then tried a simple DataContract...

    [DataContract]

    public class TestEnum

    {

       [DataMember]

       public string EnumVal {get; set;}

    }

    but could not get it to work without throwing an exception.  Is this something that should work if I keep looking into it?

     

    Thanks.

    Monday, June 28, 2010 4:40 PM
  • There isn't any documentation or samples on replacing the default data contract serializer for a single type. I suppose you could create an instance of the default data contract serializer in your custom data contract serializer and forward calls to it as needed.

    I did a little more research into the surrogate solution and I don't think it's going to be helpful to you. You must use the surrogate on both the service and client side and that defeats the purpose of what you are trying to accomplish (not having to update the client to get this to work).

    I think the best solution is to not use enums. I'm sorry I don't have a better answer for you.

    Thanks,

    Michael Green [MSFT]

    Tuesday, June 29, 2010 11:26 PM
  • Mike...

     

    There is no alternative to using enums...first our huge project is way too far into development to change now.   Second our constants need to be in the wsdl as a datacontract.

     

    we would have to release it and then work on a custom  datacontract serializer for the next release.

     

    My question is why do you think the surrogate needs to be on server AND client side?  if enums are serialized as strings, then why couldn't a surrogate be used only on the server side to deserialize it as a string and not an enum which has the unfortunate side-effect of throwing an exception?

    Wednesday, June 30, 2010 9:24 PM
  • This is genius. I absolutely love this solution, it is simple and effective. This can be improved even further with a slight change in the DataContract:

    [DataContract]
    public class Car
    {
        [DataMember]
        public string Make { get; set; }
    
        [DataMember(Name = "Color")]
        private WebEnum<Colors> color;
        public Colors Color 
        {
            get { return color.Value; }
            set { color.Value = value; }
        }
    }

    The main advantage of this way of using the WebEnum<T> wrapper is that the client doesn't need to be aware of it. This saves you from changing existing code where implicit type conversion magic doesn't do the trick (think of LINQ expressions).

    And by explicitly setting the "Name" property of the DataMemerAttribute, you retain compatibility with previously generated client stubs.

    You can even declare the WebEnum<T> class internal because it only has to be used in the shared assembly.

    The only downside is that it requires a bit more typing. Also, enforcing this as a convention with your co-developers might be tedious.



    • Edited by agnostic Wednesday, September 25, 2013 1:00 PM
    Wednesday, September 25, 2013 12:59 PM