Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to customize the process employed by WCF when serializing contract method arguments?

I would like to formulate a contrived scenario, which nevertheless has firm actual basis. Imagine a collection type COuter, which is a wrapper around an instance of another collection type CInner. Both implement IList (never mind the T).

Furthermore, a COuter instance is buried inside some object graph, the root of which (let us refer to it as R) is returned from a WCF service method.

My question is how can I customize the WCF serialization process, so that when R is returned, the request to serialize the COuter instance will be routed through my code, which will extract CInner and pass it to the serializer instead. Thus the receiving end still gets R, only no COuter instance is found in the object graph.

I hoped that How does WCF serialize the method call? will contain the answer, unfortunately the article mentioned there (http://msdn.microsoft.com/en-us/magazine/cc163569.aspx) only barely mentions that advanced serialization scenarios are possible using IDataContractSurrogate interface, but no details are given. I am, on the other hand, would really like to see a working example.

Thank you very much in advance.

EDIT

I have created a trivial WCF sample, which demonstrates the issue. The archive is located here - https://docs.google.com/leaf?id=0B2pbsdBJxJI3NzFiNjcxMmEtMTM5Yy00MWY2LWFiMTUtNjJiNjdkYTU1ZTk4&sort=name&layout=list&num=50

It contains three small projects:

  • HelloServiceAPI - contains the service interface and the argument types
  • Host - the HelloService host
  • Client - a simple console client.

The service defines one method, which returns an instance of the HelloServiceResult type, which contains a reference to COuterList type, which wraps CInnerList type. The reference is specified as IMyListInterface, where both COuterList and CInnerList implement this interface. What I need is that when the result is serialized before being transmitted to the client, the COuterList reference be replaced with the wrapped CInnerList reference. I know this can be done by utilizing the existing abilities of WCF, I just do not know how.

like image 637
mark Avatar asked Feb 27 '23 20:02

mark


1 Answers

Here is how you implement your own Surrogate:

class YourCustomTypeSurrogate : IDataContractSurrogate
{
    public Type GetDataContractType(Type type)
    {
        // Just for reference
        //if (typeof(OldType).IsAssignableFrom(type))
        //{
        //    return typeof(NewType);
        //}
        return type;
    }
    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // This method is called on serialization.
        //if (obj is OldType)
        //{
        //    // ... use the XmlSerializer to perform the actual serialization.
        //    NewType newObj = new NewType();
        //    return newObj;
        //}
        return obj;
    }
    public object GetDeserializedObject(object obj, Type targetType)
    {
        // This method is called on deserialization.
        // If PersonSurrogated is being deserialized...
        //if (obj is NewType)
        //{
        //    OldType newObj = new OldType();
        //    return newObj;
        //}
        return obj;
    }
    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        // This method is called on schema import.

        //if (typeNamespace.Equals("Your Type Namespace"))
        //{
        //    if (typeName.Equals("NewType"))
        //    {
        //        return typeof(OldType);
        //    }
        //}
        return null;
    }

    public System.CodeDom.CodeTypeDeclaration ProcessImportedType(System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit)
    {
        // Not used in this sample.
        // You could use this method to construct an entirely 
        // new CLR type when a certain type is imported, or modify a generated
        // type in some way.
        return typeDeclaration;
    }


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

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

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
        // Not used in this sample
    }
}

Then you create a custom Serializer Operation Behavior :

public class CustomDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior
{
    public CustomDataContractSerializerOperationBehavior(OperationDescription operationDescription) : base(operationDescription) { }

    public override XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(
            type /*typeof OldType*/,
            knownTypes,
            int.MaxValue /*maxItemsInObjectGraph */,
            false /*ignoreExtensionDataObject*/,
            true /*preserveObjectReferences*/,
            new YourCustomTypeSurrogate());
    }

    public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes)
    {
        return new DataContractSerializer(
            type /*typeof OldType*/,
            knownTypes,
            int.MaxValue /*maxItemsInObjectGraph */,
            false /*ignoreExtensionDataObject*/,
            true /*preserveObjectReferences*/,
            new YourCustomTypeSurrogate());
    }
}

After that, you create an attribute to apply the above operation behavior to an operation contract :

public class CustomDataContractFormatAttribute : Attribute, IOperationBehavior
{
    public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
    {
    }

    public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }

    public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
    {
        ReplaceDataContractSerializerOperationBehavior(description);
    }

    public void Validate(OperationDescription description)
    {
    }

    private static void ReplaceDataContractSerializerOperationBehavior(OperationDescription description)
    {
        DataContractSerializerOperationBehavior dcs = description.Behaviors.Find<DataContractSerializerOperationBehavior>();

        if (dcs != null)
            description.Behaviors.Remove(dcs);

        description.Behaviors.Add(new CustomDataContractSerializerOperationBehavior(description));
    }
}

And finally, you apply this Attribute to an operation :

    [OperationContract]
    [CustomDataContractFormat]
    void DoWork();

If you want to apply this to whole service, then you customize Service Behavior instead of Operation Behavior.

Here are the references that were used to create this example :

http://msdn.microsoft.com/en-us/library/system.runtime.serialization.idatacontractsurrogate.aspx

http://www.danrigsby.com/blog/index.php/2008/03/07/xmlserializer-vs-datacontractserializer-serialization-in-wcf/

http://www.danrigsby.com/blog/index.php/2008/04/10/specifying-a-different-serializer-per-endpoint-in-wcf/

http://social.msdn.microsoft.com/forums/en-US/wcf/thread/e4d55f3f-86d1-441d-9187-64fbd8ab2b3d/

like image 195
decyclone Avatar answered May 01 '23 10:05

decyclone