Preserving Object Reference in WCF

By default object references are not preserved by the DataContractSerializer; Values of an object referenced multiple times is serialized multiple times. If the object is part of mutual (cyclic) reference (e.g. circular linked list) an exception is thrown during serialization.

DataContractSerializer can be made to preserve object reference by passing true for parameter PreserveObjectReference when constructing DataContractSerializer as shown below.

new DataContractSerializer(type, name, ns, knownTypes,

                0x7FFF /*maxItemsInObjectGraph*/,

                false/*ignoreExtensionDataObject*/,

   true/*preserveObjectReferences*/,

                null/*dataContractSurrogate*/);

Enabling in Service Operation

To enable this option in service operation one must pass an instance of DataContractSerializer to the WCF runtime. This can be done by sub classing DataContractSerializerOperationBehavior and overriding CreateSerializer method as shown below.

    class ReferencePreservingDataContractSerializerOperationBehavior

      :DataContractSerializerOperationBehavior

    {

        public ReferencePreservingDataContractSerializerOperationBehavior(

          OperationDescription operationDescription)

          : base(operationDescription) { }

        public override XmlObjectSerializer CreateSerializer(

          Type type, string name, string ns, IList<Type> knownTypes)

        {

            return CreateDataContractSerializer(type, name, ns, knownTypes);

        }

        private static XmlObjectSerializer CreateDataContractSerializer(

          Type type, string name, string ns, IList<Type> knownTypes)

        {

            return CreateDataContractSerializer(type, name, ns, knownTypes);

        }

        public override XmlObjectSerializer CreateSerializer(

          Type type, XmlDictionaryString name, XmlDictionaryString ns,

          IList<Type> knownTypes)

        {

            return new DataContractSerializer(type, name, ns, knownTypes,

                0x7FFF /*maxItemsInObjectGraph*/,

                false/*ignoreExtensionDataObject*/,

                true/*preserveObjectReferences*/,

                null/*dataContractSurrogate*/);

        }

    }

The behavior must be added to all the operations on the server side contract. For a self hosted server it can be done as shown below.

        ServiceHost host = new ServiceHost(

                             typeof(FooContractImpl), new Uri(address));

        host.AddServiceEndpoint(typeof(FooContract), binding, address);

        foreach (ServiceEndpoint endpoint in host.Description.Endpoints)

            SetDataContractSerializerBehavior(endpoint.Contract);

        host.Open();

Similary the behavior must be added to all the operations on the client side as shown below.

        ChannelFactory<FooContract> factory = new ChannelFactory<FooContract>(binding, new EndpointAddress(address));

        SetDataContractSerializerBehavior(factory.Endpoint.Contract);

        FooContract proxy = factory.CreateChannel();

The SetDataContractSerializerBehavior is defined as shown below.

    private static void SetDataContractSerializerBehavior(ContractDescription contractDescription)

    {

        foreach (OperationDescription operation in contractDescription.Operations)

        {

    operation.Behaviors.Add(new ReferencePreservingDataContractSerializerOperationBehavior(operation));

        }

    }

Enabling via Attribute

This is all good. However wouldn’t it be great if it is possible avoid all the imperative code and instead define an attribute as shown below?

[ServiceContract]

public interface FooContract

{

    [OperationContract]

    [ReferencePreservingDataContractFormat]

    Node EchoNode(Node node);

}

Yes this can be done by defining a custom attribute that implements IOperationBehavior as shown below.

public class ReferencePreservingDataContractFormatAttribute : Attribute, IOperationBehavior

{

    #region IOperationBehavior Members

    public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)

    {

    }

    public void ApplyClientBehavior(OperationDescription description,System.ServiceModel.Dispatcher.ClientOperation proxy)

    {

        IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);

        innerBehavior.ApplyClientBehavior(description, proxy);

    }

    public void ApplyDispatchBehavior(OperationDescription description,System.ServiceModel.Dispatcher.DispatchOperation dispatch)

    {

        IOperationBehavior innerBehavior = new ReferencePreservingDataContractSerializerOperationBehavior(description);

        innerBehavior.ApplyDispatchBehavior(description, dispatch);

    }

    public void Validate(OperationDescription description)

    {

    }

    #endregion

}

The complete sample is attached.

Program.cs