4
votes

We have a WCF service that exposes a "Customer" type, i.e.:

[DataContract(Name = "CustomerData", Namespace = "http://www.testing.com")]
public partial class Customer
{   
    [DataMember]
    public CustomerLevel Level
    {
        get;
        set;
    }   
}

You can see the above type has a property that is an enumeration type. The definition for this enum is:

[IgnoreCoverage]
[DataContract(Namespace = "http://www.testing.com"")]
public enum CustomerLevel : int
{

    [EnumMember(Value = "Platinum")]
    Platinum = 1,

    [EnumMember(Value = "Gold")]
    Gold = 2,

    [EnumMember(Value = "Silver")]
    Silver = 3,

    [EnumMember(Value = "Bronze")]
    Bronze = 4,
}

The service works fine as long as the server sends a valid enumeration for each customer that it returns. However, if the service returns a CustomerLevel that is not defined in the enumeration the service call times out.

An example of a bad CustomerLevel value might be:

customer.Level = (CustomerLevel)0;

The service also times out if the client attempts to send a non-defined value.

Is there any way to allow the non-defined value to flow through to both the client and server and let each of them handle the bad value on their own?

5

5 Answers

3
votes

I don't think you're going to get bogus enums to work. What would they deserialize into? If you mean for the client to send integers, then change the type to int, and convert it into the enum on your own (with your own error handling).

As to whether the client should time out, please tell us what sort of client you're using. Also, I recommend you look at the network traffic and see how the service responded, if at all. Also look in the Windows event logs to see if the service complained at all. Finally, you may want to turn on WCF tracing to see how the service is reacting to this.

2
votes

You can do this using a custom IDataContractSurrogate to map the enum to an int then back to an enum.

The documentation for creating such surrogates is here: http://msdn.microsoft.com/en-us/library/ms733064.aspx

Here is a generic version I developed that can handle a list of Enum types. You specify this in the constructor for your DataContractSerializer. For more information, see my blog post here: http://www.shulerent.com/2012/08/13/handling-invalid-enum-values-in-a-datacontractserializer/

/// <summary>
/// IDataContractSurrogate to map Enum to int for handling invalid values
/// </summary>
public class InvalidEnumContractSurrogate : IDataContractSurrogate
{
    private HashSet<Type> typelist;

    /// <summary>
    /// Create new Data Contract Surrogate to handle the specified Enum type
    /// </summary>
    /// <param name="type">Enum Type</param>
    public InvalidEnumContractSurrogate(Type type)
    {
        typelist = new HashSet<Type>();
        if (!type.IsEnum) throw new ArgumentException(type.Name + " is not an enum","type");
        typelist.Add(type);
    }

    /// <summary>
    /// Create new Data Contract Surrogate to handle the specified Enum types
    /// </summary>
    /// <param name="types">IEnumerable of Enum Types</param>
    public InvalidEnumContractSurrogate(IEnumerable<Type> types)
    {
        typelist = new HashSet<Type>();
        foreach (var type in types)
        {
            if (!type.IsEnum) throw new ArgumentException(type.Name + " is not an enum", "type");
            typelist.Add(type);
        }
    }

    #region Interface Implementation

    public Type GetDataContractType(Type type)
    {
        //If the provided type is in the list, tell the serializer it is an int
        if (typelist.Contains(type)) return typeof(int);
        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        //If the type of the object being serialized is in the list, case it to an int
        if (typelist.Contains(obj.GetType())) return (int)obj;
        return obj;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        //If the target type is in the list, convert the value (we are assuming it to be int) to the enum
        if (typelist.Contains(targetType)) return Enum.ToObject(targetType, obj);
        return obj;
    }

    public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection<Type> customDataTypes)
    {
        //not used
        return;
    }

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        //Not used
        return null;
    }

    public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType)
    {
        //not used
        return null;
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        //not used
        return null;
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        //not used
        return typeDeclaration;
    }

    #endregion
}
2
votes

The answer to the question, for me at least, was setting IsRequired = false. Like this:

[DataMember(IsRequired = false)]
Nullable<MyEnum> Prop {get;set;}

This adds the nillble="true" to the wsdl properly...

1
votes

I don't think there is any way to expose undefined enums using a DataContract.

For propagating exceptions, you might try enabling "IncludeExceptionDetailsInFaults" - see the MSDN documentation for details.

What I usually do is implement an IErrorHandler server-side that logs exceptions and promotes exceptions to Faults - there is some good stuff on IDesign's web site.

0
votes

Try removing the EnumMember attributes from your enumeration values.

Also you do not need to mark you enumeration as a DataContract as the DataContractSerializer will automatically serialize any enumerations that you use in your existing DataContracts.


What I posted above does not work. I don't think it is possible to use the DataContractSerializer to serialize enumeration values that are invalid.