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); }
10 коммент.:
Works nicely, but what to do when sending the objects from client back to Server?
You must apply this attribute to the client interfaces as well.
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!
Great Article! I am wondering how or where I can apply this attribute to the client interface. thanks!
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; }
}
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
Thanks!
Works perfectly with EF POCO objects serialized over WCF web services!
R
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!!!
Отправить комментарий