Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

WCF cyclic references support

WCF DataContract serializer isn't by default aware of cyclic object graphs. If you encounter the Object graph for type 'X.Y.Z' contains cycles and cannot be serialized if reference tracking is disabled error - read to the end.

There is a simple solution and it's well documented in the Preserving Object Reference in WCF article. However the code is very chaotic and contains mistakes. I've made several classes to support a simple and powerful control over the DataContract serialization parameters.

Support classes

  1. ApplyCyclicDataContractSerializerOperationBehavior

    An operation behavior and The DataContractSerializerOperationBehavior descendant. Used to inject custom configured DataContractSerializer instances into WCF pipeline.

    internal class ApplyCyclicDataContractSerializerOperationBehavior : DataContractSerializerOperationBehavior{
        private readonly Int32 _maxItemsInObjectGraph;
        private readonly bool _ignoreExtensionDataObject;
        private readonly bool _preserveObjectReferences;
    
        public ApplyCyclicDataContractSerializerOperationBehavior(OperationDescription operationDescription, Int32 maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences)
            : base(operationDescription) {
            _maxItemsInObjectGraph = maxItemsInObjectGraph;
            _ignoreExtensionDataObject = ignoreExtensionDataObject;
            _preserveObjectReferences = preserveObjectReferences;
        }
    
        public override XmlObjectSerializer CreateSerializer(Type type, String name, String ns, IList<Type> knownTypes) {
            return (new DataContractSerializer(type, name, ns, knownTypes, _maxItemsInObjectGraph, _ignoreExtensionDataObject, _preserveObjectReferences, null /*dataContractSurrogate*/));
        }
    
        public override XmlObjectSerializer CreateSerializer(Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes) {
            return (new DataContractSerializer(type, name, ns, knownTypes, _maxItemsInObjectGraph, _ignoreExtensionDataObject, _preserveObjectReferences, null /*dataContractSurrogate*/));
        }
    
    }
    
  2. CyclicReferencesAwareContractBehavior

    A contract behavior. Used to apply the DataContractSerializerOperationBehavior operation behavior to every operation within a contract.

    public class CyclicReferencesAwareContractBehavior : IContractBehavior{
        private const Int32 maxItemsInObjectGraph = 0xFFFF;
        private const bool ignoreExtensionDataObject = false;
    
        private bool _on;
    
        public CyclicReferencesAwareContractBehavior(bool on) {
            _on = on;
        }
    
        #region IContractBehavior Members
    
        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) {
        }
    
        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, ClientRuntime clientRuntime) {
            ReplaceDataContractSerializerOperationBehaviors(contractDescription, _on);
        }
    
        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) {
            ReplaceDataContractSerializerOperationBehaviors(contractDescription, _on);
        }
    
        internal static void ReplaceDataContractSerializerOperationBehaviors(ContractDescription contractDescription, bool on) {
            foreach (var operation in contractDescription.Operations) {
                ReplaceDataContractSerializerOperationBehavior(operation, on);
            }
        }
    
        internal static void ReplaceDataContractSerializerOperationBehavior(OperationDescription operation, bool on) {
            if (operation.Behaviors.Remove(typeof(DataContractSerializerOperationBehavior)) || operation.Behaviors.Remove(typeof(ApplyCyclicDataContractSerializerOperationBehavior))) {
                operation.Behaviors.Add(new ApplyCyclicDataContractSerializerOperationBehavior(operation, maxItemsInObjectGraph, ignoreExtensionDataObject, on));
            }
        }
    
        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) {
        }
    
        #endregion
    }
    
  3. CyclicReferencesAwareAttribute

    The most valuable member of the triade. Used to apply cycling support policy to the whole service interface or an individual interface operation.

    [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Method)]
    public class CyclicReferencesAwareAttribute : Attribute, IContractBehavior, IOperationBehavior{
        private readonly bool _on = true;
    
        public CyclicReferencesAwareAttribute(bool on) {
            _on = on;
        }
    
        public bool On {
            get { return (_on); }
        }
    
        #region IOperationBehavior Members
    
        void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {
        }
    
        void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.ClientOperation clientOperation) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehavior(operationDescription, On);
        }
    
        void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription, System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehavior(operationDescription, On);
        }
    
        void IOperationBehavior.Validate(OperationDescription operationDescription) {
        }
    
        #endregion
    
        #region IContractBehavior Members
    
        void IContractBehavior.AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) {
        }
    
        void IContractBehavior.ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehaviors(contractDescription, On);
        }
    
        void IContractBehavior.ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime) {
            CyclicReferencesAwareContractBehavior.ReplaceDataContractSerializerOperationBehaviors(contractDescription, On);
        }
    
        void IContractBehavior.Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) {
        }
    
        #endregion
    }
    

Examples:

CyclicReferencesAware applied to the whole interface:

[ServiceContract]
[CyclicReferencesAware(true)]
public interface TestFacade
{
    [OperationContract]
    void Hello(SomeData data);
}

CyclicReferencesAware applied to individual operation(s):

[ServiceContract]
public interface TestFacade
{
    [OperationContract]
    [CyclicReferencesAware(true)]
    void Hello(SomeData data);
}

CyclicReferencesAware applied to the whole interface and individual operation(s):

[ServiceContract]
[CyclicReferencesAware(false)]
public interface TestFacade
{
    [OperationContract]
    [CyclicReferencesAware(true)]
    void Hello(SomeData data);
}

10 коммент.:

Анонимный комментирует...

Works nicely, but what to do when sending the objects from client back to Server?

Chabster комментирует...

You must apply this attribute to the client interfaces as well.

matt комментирует...

Thanks for the nice article.

I assume that this attribute will need to be added to the service proxy either generated or coded manually? I'm using svcutil.exe to generate the proxies - there appears to be a mechanism to support the custom attribute:

http://msdn.microsoft.com/en-us/library/aa717040(VS.85).aspx

Анонимный комментирует...

Sweet, works like a charm!

Eric Kaufman комментирует...

Great Article! I am wondering how or where I can apply this attribute to the client interface. thanks!

John Haigh комментирует...

For anyone coming across this like I did. With the new .NET 3.5 SP1 you no longer need to pass an instance of DataContractSerializer to the WCF runtime and write the above. Simply specify the DataContract IsRefence to true.

[DataContract(IsReference = true)]
See an article here: http://www.zamd.net/2008/05/20/DataContractSerializerAndIsReferenceProperty.aspx
I.e.
[DataContract(IsReference = true)]

public class Employee

{

[DataMember]

public string Name { get; set; }

[DataMember]

public Employee Manager { get; set; }

}

Alexis Alulema комментирует...

Hey chabster and all u guys.
Was anybody able to send object from Silverlight back to server? It seems the classes u r using here don't exist in Silverlight.

Any idea? Thanks in advance

Rob комментирует...

Thanks!

Works perfectly with EF POCO objects serialized over WCF web services!

R

Michael комментирует...

Hi, great code! Is there a way to do this breadth first? I noticed that if I return this in a SOAP response the first object has the information as expected. However, then when it drills down to the type that cyclically references the first type, it populates the objects there. So on the top level I end up with 1 full object and a bunch of references. Ideally, I would like to have all the main data at the top level and the references when you drill down.

Thanks,

Человек? комментирует...

Hi! Could we use [CyclicReferencesAware(true)] with WCF RIA Services in some way?
Thanks!!!

Отправить комментарий

Copyright 2007-2011 Chabster