Dot Net Thoughts

January 19, 2008

Serializing with WCF

Filed under: csharp,Uncategorized — dotnetthoughts @ 1:23 pm
Tags: , , ,

First of all, I want to apologize for the amount of time it has taken me to put up a new blog post. I typically try to write once a week, but my life has been crazy as of late. The outcome of all of the madness is that I will be packing up my family and moving from Portland, Oregon to Denver, Colorado at the end of this month. (My wife has been accepted into a Master’s degree program there!)Because of this, posts for the next couple of months may be sporadic, as well. (I’m looking for work in the Denver area. If anybody has any leads, let me know… )

In celebration of my move, I thought that I would put together an example that demonstrates two different ways that WCF serializes objects. (This posting will simply show different ways the engine can work. I hope to actually wire it into the WCF pipeline in a future post.) Since I spent last night packing all of my books into boxes, I thought that maybe I’d find a way to organize them. The goal of this application is to create a manifest list allows me quickly find any book that I’ve packed away in a moving box.

As always, the complete code is at the end of the blog post. The amount of code with this post is a little bit greater than usual. I’ll try and write code directly related to the topic within the text, but I would recommend copying the code to studio if you want to follow along.

Since my main objective is to manage books and boxes, I’m going to begin by simply creating a very basic object for each. My Book object will contain two different properties (Title and Author), and my Box object will simply contain an Id. Since we will be using the WCF engine to do our serialization we will need to mark the classes and properties we wish to expose to the serialization engine with the [DataContract] and the [DataMember] attributes.

 
    #region Book Class            

    [DataContract] 
    public class Book 
    { 
        [DataMember] 
        public string Title { get; set; }            

        [DataMember] 
        public string Author { get; set; } 
    } 
    #endregion            

    #region Box Class 
    [DataContract] 
    public class Box 
    { 
        [DataMember] 
        public int Id { get; set; } 
    } 
   #endregion 

I now need a method to associate books with boxes. In order to keep my Book and Box object as loosely coupled as possible, I will add a Manifest object to the project which will be responsible for maintaining the relationship between the two. The Manifest object will expose three properties. The Books property will contain a collection of all of the Books on my bookshelf. The Boxes property will contain all of the Boxes I’m using to move. The LineItems property will contain information as to which book is in which box.

 
        //Property inside of the Manifest Class. 
        private List<Box> _boxes = new List<Box>(); 
        [DataMember] 
        public List<Box> Boxes 
        { 
            get 
            { 
                return _boxes; 
            } 
            private set 
            { 
                _boxes = value; 
            } 
        } 
        //LineItems and Books here… 

Since we are using WCF, the serializer will require both a getter and a setter for every object it serializes. I don’t really want to give consumers of my class the ability to blow away my boxes collection, so I have explicitly created a private setter. This allows WCF the access it needs, while denying direct access to other consumers.

LineItems are added to the collection by calling the AddLineItem method. This method adds the book to the book collection, adds a box to the box collection, and creates a relationship between the two by adding a new LineItem to the LineItems collection. It is important to note that the association created in the LineItems collection does not contain new instances of books and boxes, rather it contains a reference to the master copy in the Books and Boxes collection. (In other words, the association is by reference.)

 
        public void AddLineItem(Book book, Box box) 
        { 
            Books.Add(book); 
            Boxes.Add(box); 
            LineItems.Add(new LineItem(book, box)); 
        } 

I’ve overridden the ToString on the manifest object to display the complete list of books and boxes that it contains. There is also a CreateShippingManifest method which will be used to create a generic manifest of three books contained in two boxes. (See code below.)

To test our code, we’ll create our sample manifest and view the results.

 
      Manifest manifest = CreateShippingManifest(); 
      Console.WriteLine(manifest.ToString()); 

The results which come back are as follows:

 
        Box 1   A Brief History of Time 
        Box 1   Guards Guards 
        Box 2   The Reptile Room 

Now, we’ll make an assumption that I made a mistake in entering a box label into the program. Instead of box 1, I meant the Id to be box 100. We’ll make the change to the box id, and the manifest will automatically pick up the results.

 
        Manifest manifest = CreateShippingManifest(); 
        manifest.Boxes[0].Id = 100; 
        Console.WriteLine(manifest.ToString()); 

Results:

 
        Box 100 A Brief History of Time 
        Box 100 Guards Guards 
        Box 2   The Reptile Room

Now, let’s add some code to serialize and deserialize our manifest. This is actually a fairly simple process. The first method will serialize the data to disc (as Xml), the second method will read the file and deserialize it into a new object.

 
      public void Serialize(string fileName) 
      { 
        DataContractSerializer ds = new DataContractSerializer(this.GetType());            

         using (Stream stream = File.Create(fileName)) 
         { 
             ds.WriteObject(stream, this); 
         } 
      }   

      public void Deserialize(string fileName) 
      { 
          using (Stream stream = File.OpenRead(fileName)) 
          { 
              DataContractSerializer ds = new DataContractSerializer(this.GetType()); 
              Manifest manifest = (Manifest)ds.ReadObject(stream); 
              this.Books = manifest.Books; 
              this.Boxes = manifest.Boxes; 
              this.LineItems = manifest.LineItems; 
          } 
      } 

We can now run code similar to that we ran above. Between steps, let’s serialize and deserialize our objects, though.

 
      static void Main(string[] args) 
      { 
          string serializationFile = @"c:\tempmw\Manifest.xml";            

          Manifest manifest = CreateShippingManifest(); 
          manifest.Serialize(serializationFile);            

          Manifest manifest2 = new Manifest(); 
          manifest2.Deserialize(serializationFile);            

          Console.WriteLine("Object after deserialization"); 
          Console.WriteLine(manifest2.ToString());            

          manifest2.Boxes[0].Id = 100; 
          Console.WriteLine(manifest2.ToString());            

          Console.ReadLine(); 
      } 

This yields the results:

 
      Object after deserialization 
      Box 1   A Brief History of Time 
      Box 1   Guards Guards 
      Box 2   The Reptile Room            

      Box 1   A Brief History of Time 
      Box 1   Guards Guards 
      Box 2   The Reptile Room 

But wait a minute! What happened here? We clearly changed the Id of the first box to be 100, yet the results still state that our box is Box 1! The code looks very similar to what we used in the non-serialized objects. The reason that this happens is because of the way the default instance of the DataContractSerializer writes out the results. If you take a look at the Xml file that is created from our serializer, you’ll see results similar to the following:

 
<Books> 
  <Book> 
    <Author>Stephen Hawking</Author> 
    <Title>A Brief History of Time</Title> 
  </Book> 
  … 
</Books> 
<Boxes> 
  <Box> 
    <Id>1</Id> 
  </Box> 
  … 
</Boxes> 
<LineItems> 
  <Manifest.LineItem> 
    <Book> 
      <Author>Stephen Hawking</Author> 
      <Title>A Brief History of Time</Title> 
    </Book> 
    <Box> 
      <Id>1</Id> 
    </Box> 
  </Manifest.LineItem> 
  … 
</LineItems>  

The Xml does not contain the associations between the manifest items and the books and boxes that we set up so carefully in our code. Changing the box Id in the master collection no longer changes the box id in all of the children.

We can very easily fix this by updating the DataContractSerializer instantiation in our Serialize method. The important parameters in the new constructor are the third and fifth. The third parameter (maxItemsInObjectGraph) indicates the total number of objects that the Xml can contain. If the number of objects in the Xml is exceeded, an error will be raised. The fifth parameter (preserveObjectReferences) indicates that the associations between objects should be preserved.

 
        public void Serialize(string fileName) 
        { 
            //DataContractSerializer ds = new DataContractSerializer(this.GetType());            

            DataContractSerializer ds = new DataContractSerializer(this.GetType(), null, 100, false, true, null);            

            using (Stream stream = File.Create(fileName)) 
            { 
                ds.WriteObject(stream, this); 
            } 
        } 

The important parameters in the new constructor are the third and fifth. The third parameter (maxItemsInObjectGraph) indicates the total number of objects that the Xml can contain. If the number of objects in the Xml is exceeded, an error will be raised. The fifth parameter (preserveObjectReferences) indicates that the associations between objects should be preserved. Now, when we rerun our demo, we see that the Id of the box is correctly updated.

 
      Object after deserialization 
      Box 1   A Brief History of Time 
      Box 1   Guards Guards 
      Box 2   The Reptile Room            

      Box 100 A Brief History of Time 
      Box 100 Guards Guards 
      Box 2   The Reptile Room 

Examining the Xml now shows the Id and IdRef structures in place to reassociate the data.

 
<Manifest z:Id="1">        

<Books z:Id="2" z:Size="3"> 
   <Book z:Id="3"> 
     <Author z:Id="4">Stephen Hawking</Author> 
     <Title z:Id="5">A Brief History of Time</Title> 
   </Book> 
   ... 
</Books> 
<Boxes z:Id="12" z:Size="3"> 
   <Box z:Id="13"> 
      <Id>1</Id> 
   </Box> 
   ... 
</Boxes> 
<LineItems z:Id="15" z:Size="3"> 
   <Manifest.LineItem z:Id="16"> 
      <Book z:Ref="3" i:nil="true"/> 
      <Box z:Ref="13" i:nil="true"/> 
   </Manifest.LineItem> 
   ... 
   </LineItems> 
</Manifest> 

So, there you have it. With WCF you can serialize by value or by reference. Pretty neat stuff. This has kind of been a marathon post, I hope you could follow along. Let me know if you find anything that isn’t clear!
Code Safe!
MW

 
---------------------Sample Code-------------------------------------- 
//Console Application 
using System; 
using WcfSample;        

namespace PersistApp 
{ 
    class Program 
    { 
        static void Main(string[] args) 
        { 
            //You will need to change the filepath to a file on your local machine. 
            string serializationFile = @"c:\tempmw\Manifest.xml"; 
            //*********************************************************************        

            Manifest manifest = CreateShippingManifest();        

            Console.WriteLine("Manifest before serializing...\n"); 
            Console.WriteLine(manifest.ToString());        

            manifest.Serialize(serializationFile);        

            Manifest manifest2 = new Manifest(); 
            manifest2.Deserialize(serializationFile);        

            Console.WriteLine("Object after deserialization"); 
            Console.WriteLine(manifest2.ToString());        

            manifest2.Boxes[0].Id = 100; 
            Console.WriteLine(manifest2.ToString());        

            Console.ReadLine(); 
        }        

        private static Manifest CreateShippingManifest() 
        { 
            Manifest manifest = new Manifest();        

            Book book1 = new Book { Title = "A Brief History of Time", Author = "Stephen Hawking" }; 
            Book book2 = new Book { Title = "Guards Guards", Author = "Terry Pratchett" }; 
            Book book3 = new Book { Title = "The Reptile Room", Author = "Lemony Snicket" };        

            Box box1 = new Box { Id = 1 }; 
            Box box2 = new Box { Id = 2 };        

            manifest.AddLineItem(book1, box1); 
            manifest.AddLineItem(book2, box1); 
            manifest.AddLineItem(book3, box2);        

            return manifest; 
        }        

    } 
}        

//*************************************************************        

//Classes        

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Runtime.Serialization; 
using System.Text;        

namespace WcfSample 
{        

    #region Manifest 
    [DataContract] 
    public class Manifest 
    { 
        private List<Box> _boxes = new List<Box>(); 
        private List<Book> _books = new List<Book>(); 
        private List<LineItem> _lineItems = new List<LineItem>();        

        [DataMember] 
        public List<Box> Boxes 
        { 
            get 
            { 
                return _boxes; 
            } 
            private set 
            { 
                _boxes = value; 
            } 
        }        

        [DataMember] 
        public List<Book> Books 
        { 
            get 
            { 
                return _books; 
            } 
            private set 
            { 
                _books = value; 
            } 
        }        

        [DataMember] 
        public List<LineItem> LineItems 
        { 
            get 
            { 
                return _lineItems; 
            } 
            private set 
            { 
                _lineItems = value; 
            } 
        }        

        public void AddLineItem(Book book, Box box) 
        { 
            Books.Add(book); 
            Boxes.Add(box); 
            LineItems.Add(new LineItem(book, box)); 
        }        

        public void Serialize(string fileName) 
        { 
            //DataContractSerializer ds = new DataContractSerializer(this.GetType()); 
            DataContractSerializer ds = new DataContractSerializer(this.GetType(), null, 100, false, true, null);        

            using (Stream stream = File.Create(fileName)) 
            { 
                ds.WriteObject(stream, this); 
            } 
        }        

        public void Deserialize(string fileName) 
        { 
            using (Stream stream = File.OpenRead(fileName)) 
            { 
                DataContractSerializer ds = new DataContractSerializer(this.GetType()); 
                Manifest manifest = (Manifest)ds.ReadObject(stream); 
                this.Books = manifest.Books; 
                this.Boxes = manifest.Boxes; 
                this.LineItems = manifest.LineItems; 
            } 
        }        

        public override string ToString() 
        { 
            StringBuilder sb = new StringBuilder();        

            foreach (LineItem li in this.LineItems) 
            { 
                sb.AppendLine(String.Format("Box {0}\t{1}", li.Box.Id, li.Book.Title)); 
            }        

            return sb.ToString(); 
        } 
    #endregion        

        #region LineItemClass 
        [DataContract] 
        public class LineItem 
        { 
            private Book _book; 
            private Box _box;        

            public LineItem(Book book, Box box) 
            { 
                _box = box; 
                _book = book; 
            }        

            [DataMember] 
            public Box Box 
            { 
                get 
                { 
                    return _box; 
                } 
                private set 
                { 
                    _box = value; 
                } 
            }        

            [DataMember] 
            public Book Book 
            { 
                get 
                { 
                    return _book; 
                } 
                private set 
                { 
                    _book = value; 
                } 
            } 
        } 
        #endregion 
    }         

    #region Book Class        

    [DataContract] 
    public class Book 
    { 
        [DataMember] 
        public string Title { get; set; }        

        [DataMember] 
        public string Author { get; set; } 
    } 
    #endregion        

    #region Box Class 
    [DataContract] 
    public class Box 
    { 
        [DataMember] 
        public int Id { get; set; }        

    } 
    #endregion 
}        

Create a free website or blog at WordPress.com.