Serializing Cyclic Graphs of objects with WCF

Abstract

In this blog post I will be discussing how the serialize cyclic graphs of objects using WCF, including data contracts and POCO’s ( Plain Old CLR Objects).

Introduction

WCF allows you to serialize any number of objects over the network or to disk using the DataContractSerializer class. However, some issues can arise when you have cycles in your object graph, and especially when you want some shared objects to stay shared!

Serializing cyclic objects

Let’s look at this with a couple of examples. First I want some datacontracts with possible cycles:

  [DataContract(Namespace=Namespaces.U2U, Name="address", IsReference=false)]   public class Address   {     [DataMember(Name="street", Order=1)]     public string Street { get; set; }     [DataMember(Name="city", Order=2)]     public string City { get; set; }   }    [DataContract(Namespace = Namespaces.U2U, Name = "person", IsReference = false)]   public class Person   {     [DataMember(Name="name", Order=1)]     public string Name { get; set; }     [DataMember(Name="age", Order=2)]     public int Age { get; set; }     [DataMember(Name="partner",Order=3)]     public Person Partner { get; set; }     [DataMember(Name = "address", Order = 4)]     public Address Address { get; set; }   } 

 

I’ll create a person object with a reference to another person. Since they are partners, they will point to each other.

image

DataContractSerializer ser = new DataContractSerializer(typeof(Person)); string path = Path.Combine( Environment.CurrentDirectory, "cartoon.xml" ); using (FileStream ms = File.Open(path, FileMode.Create, FileAccess.Write)) {   Address home = new Address { Street = "ToonLane", City = "ToonTown" };   Person tom = new Person { Name = "Tom", Age = 4, Address = home };   Person jerry = new Person { Name = "Jerry", Age = 2, Address = home };   tom.Partner = jerry;   jerry.Partner = tom;   ser.WriteObject(ms, tom); }

So what happens when you try this? You’ll get an exception:

image

[NOTE: By the way, this is much better then in .NET 1.0 with the XmlSerializer, it would serialize the first object, then follow the link to the second, then back the to first, then back to the second, until your disk ran out of space :) Now XmlSerializer will also throw an exception if you try to serialize cycles.]

Using IsReference

To make the exception go away, you add IsReference=true to the Person’s DataContract:

The result now looks like this:

<person z:Id="i1" 
xmlns="urn://www.u2u.be/samples/wcf/2009"
xmlns:i=http://www.w3.org/2001/XMLSchema-instance
xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/"> <name>Tom</name> <age>4</age> <partner z:Id="i2"> <name>Jerry</name> <age>2</age> <partner z:Ref="i1"/> <address> <street>ToonLane</street> <city>ToonTown</city> </address> </partner> <address> <street>ToonLane</street> <city>ToonTown</city> </address> </person>

Look how Jerry refers back to Tom using the z:Ref! Now you can easily send objects with cycles. But wait! Both point to the same address, but now Tom and Jerry both have an address, although identical.

image

Again we can change this by changing the IsReference to true in the Address DataContract. Now the result looks like this:

<person z:Id="i1" xmlns="urn://www.u2u.be/samples/wcf/2009" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">   <name>Tom</name>   <age>4</age>   <partner z:Id="i2">     <name>Jerry</name>     <age>2</age>     <partner z:Ref="i1"/>     <address z:Id="i3">       <street>ToonLane</street>       <city>ToonTown</city>     </address>   </partner>   <address z:Ref="i3"/> </person>

Please note that now both Tom and Jerry use the same address instance, not clones.

Serializing cyclic POCO’s with WCF

“And what about POCO’s? (Plain Old Clr Objects) I hear you say. Well in that case you cannot attach the DataContract attribute of course, so you‘ll need to use another variant of the DataContractSerializer constructor:

new DataContractSerializer(typeof(PocoPerson), null, int.MaxValue, false                           , /* preserveObjectReferences */ true, null, null); 

So, using this constructor the previous example looks like this:

  public class PocoPerson   {     public string Name { get; set; }     public int Age { get; set; }     public PocoPerson Partner { get; set; }     public PocoAddress Address { get; set; }   }    public class PocoAddress   {     public string Street { get; set; }     public string City { get; set; }   }
  DataContractSerializer ser =      new DataContractSerializer(typeof(PocoPerson), null, int.MaxValue, false                               , /* preserveObjectReferences */ true, null, null);   string path = Path.Combine(Environment.CurrentDirectory, "cartoon2.xml");   using (FileStream ms = File.Open(path, FileMode.Create, FileAccess.Write))   {     PocoAddress home = new PocoAddress { Street = "ToonLane", City = "ToonTown" };     PocoPerson tom = new PocoPerson { Name = "Tom", Age = 4, Address = home };     PocoPerson jerry = new PocoPerson { Name = "Jerry", Age = 2, Address = home };     tom.Partner = jerry;     jerry.Partner = tom;     ser.WriteObject(ms, tom);
}

Running this will result in following Xml:

<PocoPerson z:Id="1"              xmlns="http://schemas.datacontract.org/2004/07/ReferenceSerialization"              xmlns:i="http://www.w3.org/2001/XMLSchema-instance"              xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">   <Address z:Id="2">     <City z:Id="3">ToonTown</City>     <Street z:Id="4">ToonLane</Street>   </Address>   <Age>4</Age>   <Name z:Id="5">Tom</Name>   <Partner z:Id="6">     <Address z:Ref="2" i:nil="true"/>     <Age>2</Age>     <Name z:Id="7">Jerry</Name>     <Partner z:Ref="1" i:nil="true"/>   </Partner> </PocoPerson>

Using a custom behavior

Of course it is not that easy to use this contructor inside a real service, because you’re not in charge of creating the serializer. But hey, WCF is very extensible right? So to plug the hole, you need to build your own Behavior, specifically a DataContractSerializerOperationBehavior.

public class CyclicSerializationBehavior : DataContractSerializerOperationBehavior {   public CyclicSerializationBehavior(OperationDescription od)     : base(od)   { }    private const bool preserveObjectReferences = true;    public override XmlObjectSerializer CreateSerializer(
Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<Type> knownTypes) { return new DataContractSerializer(type, name, ns, knownTypes,
this.MaxItemsInObjectGraph, this.IgnoreExtensionDataObject,
preserveObjectReferences, this.DataContractSurrogate); } public override XmlObjectSerializer CreateSerializer(
Type type, string name, string ns, IList<Type> knownTypes) { return new DataContractSerializer(type, name, ns, knownTypes,
this.MaxItemsInObjectGraph, this.IgnoreExtensionDataObject,
preserveObjectReferences, this.DataContractSurrogate); } }

To get this behavior installed you need to write some code to add it the the operation’s behavior, or even better is to use an IContractBehavior:

  [AttributeUsage(AttributeTargets.All)]   public class CyclicSerializationAttribute : Attribute, IContractBehavior   {     public void AddBindingParameters(ContractDescription contractDescription, 
ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ContractDescription contractDescription,
ServiceEndpoint endpoint, ClientRuntime clientRuntime) { ReplaceDataContractSerializerOperationBehavior(contractDescription); } public void ApplyDispatchBehavior(ContractDescription contractDescription,
ServiceEndpoint endpoint, DispatchRuntime dispatchRuntime) { ReplaceDataContractSerializerOperationBehavior(contractDescription); } private void ReplaceDataContractSerializerOperationBehavior(ContractDescription contractDescription) { foreach (OperationDescription operation in contractDescription.Operations) { DataContractSerializerOperationBehavior beh =
operation.Behaviors.Find<DataContractSerializerOperationBehavior>(); if (beh != null) { operation.Behaviors.Remove(beh); operation.Behaviors.Add(new CyclicSerializationBehavior(operation)); } } } public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint) { } }

I made this class into an attribute so you can apply it to your service contract or service class, and when WCF loads it it replaces the default DataContractSerialerOperationBehavior. At first I simply added it to the collection of behaviors, but then I got all kinds of strange things :)

So, to use this now you apply the attribute to the service contract:

  [ServiceContract]   [CyclicSerializationAttribute]   public interface ICyclicSerializationService   {     [OperationContract]     PocoPerson GetPocoPerson();   }

Don’t forget, you should add this to both the server en client side.


Comments (4) -

December 22. 2009 07:19 PM

payday loans

Do you have any more info on this?

payday loans

January 8. 2010 10:06 PM

how to stop panic attacks

Me and my friend were arguing about an issue similar to this! Now I know that I was right. lol! Thanks for the information you post.

how to stop panic attacks

January 13. 2010 02:48 AM

hunting dogs

That is some inspirational stuff. Never knew that opinions could be this varied. Thanks for all the enthusiasm to offer such helpful information here.

hunting dogs

January 14. 2010 10:19 PM

body jewelry plugs

Hi webmaster, commenters and everybody else !!! The blog was absolutely fantastic! Lots of great information and inspiration, both of which we all need!b Keep 'em coming... you all do such a great job at such Concepts... can't tell you how much I, for one appreciate all you do!

body jewelry plugs

January 24. 2010 01:11 AM

roulette system

I wanted to thank you for this great read!! I definitely enjoying every little bit of it I have you bookmarked to check out new stuff you post

roulette system

January 24. 2010 11:36 PM

free business cards

Me and my friend were arguing about an issue similar to this! Now I know that I was right. lol! Thanks for the information you post.

free business cards

January 31. 2010 02:41 PM

bose companion 3 series II

When I originally commented I clicked the "Notify me when new comments are added" checkbox and now each time a comment is added I get four emails with the same comment.
Is there any way you can remove me from that service?
Thanks!

bose companion 3 series II

February 1. 2010 08:12 PM

dyspraxia symptoms

I must say that overall I am really impressed with this blog.It is easy to see that you are passionate about your writing. If only I had your writing ability I look forward to more updates and will be returning.

dyspraxia symptoms

February 9. 2010 09:06 AM

teeth whitening

The ladder of success is best climbed by stepping on the rungs of opportunity.

teeth whitening

February 10. 2010 07:30 AM

unique fundraising

Thank you for the sensible critique. Me & my neighbour were preparing to do some research about that. We got a good book on that matter from our local library and most books where not as influensive as your information. I am very glad to see such information which I was searching for a long time.This made very glad Smile

unique fundraising

February 13. 2010 12:23 PM

Steger Garage Door Repair

Do you accept guest posts? I would love to write couple articles here.
I was wondering what is up with that weird gravatar??? I know 5am is early and I'm not looking my best at that hour, but I hope I don't look like this! I might however make that face if I'm asked to do 100 pushups. lol

Steger Garage Door Repair

February 14. 2010 12:04 AM

cash advance

I wanted to thank you for this great read!! I definitely enjoying every little bit of it I have you bookmarked to check out new stuff you post

cash advance

February 15. 2010 10:59 PM

derma cleanse

I admit, I have not been on this webpage in a long time... however it was another joy to see It is such an important topic and ignored by so many, even professionals. I thank you to help making people more aware of possible issues.
Great stuff as usual...

derma cleanse

February 17. 2010 08:22 PM

Free weight loss programs

Great post! I am just starting out in community management/marketing media and trying to learn how to do it well - resources like this article are incredibly helpful. As our company is based in the US, it?s all a bit new to us. The example above is something that I worry about as well, how to show your own genuine enthusiasm and share the fact that your product is useful in that case.

Free weight loss programs

February 25. 2010 09:36 PM

shop a lu

Nice information, many thanks to the author. It is incomprehensible to me now, but in general, the usefulness and significance is overwhelming. Thanks again and good luck!

shop a lu

February 28. 2010 10:41 AM

swoopo script

Resources like the one you mentioned here will be very useful to me! I will post a link to this page on my blog. I am sure my visitors will find that very useful.

swoopo script

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

Download the U2U brochure

Download Brochure

Receive the U2U Newsletter. Submit your email address:
 
 


 


Search

rss  RSS

Recent posts

None

Archive