WCF cyclic references support
Object graph for type 'X.Y.Z' contains cycles and cannot be serialized if reference tracking is disablederror - 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
-
ApplyCyclicDataContractSerializerOperationBehaviorAn operation behavior and The
DataContractSerializerOperationBehaviordescendant. Used to inject custom configuredDataContractSerializerinstances 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*/)); } }
-
CyclicReferencesAwareContractBehaviorA contract behavior. Used to apply the
DataContractSerializerOperationBehavioroperation 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 }
-
CyclicReferencesAwareAttributeThe 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); }