Jan
Jan

Reputation: 1975

Instruct WCF to deserialize type T1 as T2

Let's have a very simplified example:

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    T1 GetData();
}

[DataContract]
[KnownType(typeof(T2))]
public class T1
{ 
    [DataMember] 
    int Value { get; set; }
}

[DataContract]
public class T2: T1
{ }

I need to be able to send T1, but receive it as T2 on client side (simplified reasons: fast conversion of business objects to client readonly types; convert server pooled objects to client unpooled types. I can elaborate further if needed).

To make it little more complicated - type tree for the data being sent has more types, e.g.:

[OperationContract]
TBase GetData();

[DataContract]
[KnownType(typeof(T1))]
[KnownType(typeof(T2))]
[KnownType(typeof(T3))]
[KnownType(typeof(Tn))]
public class TBase
{ 
    [DataMember] 
    int Value { get; set; }
}

[DataContract]
public class T1: TBase { }

[DataContract]
public class T2: TBase { }

[DataContract]
public class T3: TBase { }

[DataContract]
public class Tn: TBase { }

And I need to receive all types as is, with exception of T1, that needs to be received as T2.

I tested that serialization/deserialization part is easily doable with DataContractSerializer, but I cannot find out how to instruct WCF to use different DataContractSerializer to deserialize T1.

Edit1: It looks like this should be doable byderiving custom DataContractResolver and injecting this to Operations contracts on both (client-server) WCF sides. I got this almost working - serialization-deserialization itself works as expected, WCF still fails. I'll try to post answer once I find out the reason

Upvotes: 0

Views: 151

Answers (2)

tsemer
tsemer

Reputation: 3178

Two WCF extensibility points that come to mind:

  • Deriving from DataContractResolver (as mentioned in your own answer) - lets you define a new matching between XML and its desired .Net type. Read more here.
  • Implementing IDataContractSurrogate - substitutes one .Net type with another for serialization purposes. No XML is involved. Read more here.

They are both injected in a similar way, via properties on the DataContractSerializerOperationBehavior.

Upvotes: 1

Jan
Jan

Reputation: 1975

DataContractResolver seems to be there for those tasks - so I override it:

public class MyResolver : DataContractResolver
{
    private XmlDictionaryString _typeName;
    private XmlDictionaryString _typeNamespace;

    public PooledPricesResolver()
    {
        XmlDictionary dictionary = new XmlDictionary();
        _typeName = dictionary.Add("T2");
        _typeNamespace = dictionary.Add("MyNamespace");
    }

    public override bool TryResolveType(Type type, Type declaredType, DataContractResolver knownTypeResolver, out XmlDictionaryString typeName, out XmlDictionaryString typeNamespace)
    {
        if (declaredType == typeof(T1))
        {
            typeName = _typeName; //null;
            typeNamespace = _typeNamespace; //null
            return true;
        }

        return knownTypeResolver.TryResolveType(type, declaredType, null, out typeName, out typeNamespace);
    }

    public override Type ResolveName(string typeName, string typeNamespace, Type declaredType, DataContractResolver knownTypeResolver)
    {
        if (typeName == "T2" && typeNamespace == "MyNamespace")
        {
            return typeof(T1);
        }

        return knownTypeResolver.ResolveName(typeName, typeNamespace, declaredType, null) ?? declaredType;
    }
}

Note that this can be more sophisticated. Or also more performant if you'd actauly need to resolve the type to the base type - then TryResolveType can initialize the typeName and namespace to nulls and ResolveName can just call the knownTypeResolver implementation.

To inject this resolver on server side (host) you can do the following:

//_serviceHost is ServiceHost.ServiceHost type
ContractDescription cd = _serviceHost.Description.Endpoints[0].Contract;

//the string is the name of operation for which you can do the custom (de)serialization
cd.Operations.Find("GetData")

DataContractSerializerOperationBehavior serializerBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
if (serializerBehavior == null)
{
    serializerBehavior = new DataContractSerializerOperationBehavior(operation);
    operation.Behaviors.Add(serializerBehavior);
}

serializerBehavior.DataContractResolver = new MyResolver();

//Now you can start listening by _serviceHost.Open()

On the client side, if you use hand-made proxy, you can do the following:

public class MyServiceProxy : System.ServiceModel.DuplexClientBase<IMyService>, IMyService
{
    public MyServiceProxy(InstanceContext callbackInstance, Binding binding, EndpointAddress remoteAddress)
        : base(callbackInstance, binding, remoteAddress)
    {
        ContractDescription cd = this.Endpoint.Contract;

        //the string is the name of operation for which you can do the custom (de)serialization
        cd.Operations.Find("GetData")

        DataContractSerializerOperationBehavior serializerBehavior = operation.Behaviors.Find<DataContractSerializerOperationBehavior>();
        if (serializerBehavior == null)
        {
            serializerBehavior = new DataContractSerializerOperationBehavior(operation);
            operation.Behaviors.Add(serializerBehavior);
        }

        serializerBehavior.DataContractResolver = new MyResolver();
    }

    ...

}

Client will now be able to receive types deserialized based on your needs.

Upvotes: 1

Related Questions