Handling WCF enums when client and server have different versions of enum

27 Views Asked by At

We have many WCF clients and services. These services all share a common Data package, which includes a master assembly containing all the enums used within every data contract. When this Data package has change in any of its enums, such as the addition of a new member, not all of the services need to be updated to utilize that change from a business perspective because it is not relevant to its function.

However, when this enum has an unknown value passed, it causes an error because it sends the string value, which can't map to an integer. Ideally, we'd like to write some sort of formatter or behavior to intercept enum values and determine if it's valid. If valid, proceed with the call as normal. If invalid, just use the integer value and ignore the string value. Obviously this approach will not work in cases where this enum is needed for business reasons, but most of the time these enums just need to be passed through the request.

This might be possible by adding custom headers that contain both the enum's int and string value, then handling it before deserialization, but I have been unable to get anything like this working correctly, and the headers don't seem to persist the information I require (but that might be a separate issue; I can't be sure yet).

The following is an example of a previous attempt using a surrogate, taken from this, but the error seems to occur prior to hitting this code, so does not work properly.

/// <summary>
/// Custom Data Contract Surrogate that De/Serializes enum values as their underlying values instead of strings (default WCF)
/// To avoid issues with newly introduced enum values on updated WCF services and older clients.
/// </summary>
public class EnumValueDataContractSurrogate : IDataContractSurrogate
{
    #region Interface Implementation

    public Type GetDataContractType(Type type)
    {    
        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        if(null == obj)
        {
            return obj;
        }

        if(targetType.IsNullable())
        {
            targetType = targetType.GetUnderlyingType();
        }
        
        if (targetType.IsEnum)
        {
            return EnumExtensions.ChangeToUnderlyingType(targetType, obj);
        }

        return obj;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        if ((false == targetType.IsEnum) || (null == obj))
        {
            return obj;
        }
        
        if (targetType.IsNullable())
        {
            targetType = targetType.GetUnderlyingType();
        }

        var stringObj = obj as string;
        if (null != stringObj)
        {
            return Enum.Parse(targetType, stringObj);
        }
        return Enum.ToObject(targetType, obj);
    }

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

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

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

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

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

    #endregion
}

Is what I'm trying to do even possible, ignoring the ugliness of the solution? I have written a MessageFormatter, as well, but haven't gotten that to work, either.

1

There are 1 best solutions below

0
QI You On

I have a suggestion: There is a method in WCF: BeforeCall.

The BeforeCall method is called before the client call is sent, that is, before the request is passed to the service.

It can help you verify that the enumeration data sent by the client is valid.

public object BeforeCall(string operationName, object[] inputs)
{
    
    // Judge
}

I think it's a very effective way to judge a request before it is sent.