Saturday, May 17, 2014

.NET Framework: Building a generalized WCF DataContractSerializer

I am working on sharing some samples as a common framework through a blog series. I begin this series with this post about using Window Communication Foundation's (WCF) default serializer the DataContractSerializer outside of WCF. On Legacy projects you may still be using ASMX web service or some other technology and want to start migrating serialization to a format that will allow you to easily upgrade to WCF.

WCF is a way to do web services in .NET since .NET Framework 3.0. The sample in this post will work with all versions of WCF.

Unit Testing

I find Unit Tests a great way to share assumptions, document usage, and learn someone's coding style. In the sample I provided a sample usage of XmlDataContractSerializer in the test named SerializeToXmlString_BasicUseCaseWorks. Yes I do not have an Assert but as a side not this unit test does test that the Serialization happens without exception. 

As MSTest like most unit testing frameworks fails a test if an exception is thrown. I use MSTest because I am not doing anything that requires a particular unit testing framework. Feel free to use the one of your choice. I am not espousing its use over others.

[TestMethod]
        public void SerializeToXmlString_BasicUseCaseWorks()
        {
            var strategy = new XmlDataContractSerializer<ContactDto>();
            var data = CreateSampleData(); 
            var xml = strategy.SerializeToXmlString(data, true);
        }
 I share with you some of my favorite places using the Contact Data Transport Object (DTO) class ContactDto. The CreateSampleData mimics a Repository method, creating a collection of some of my favorite places statically although you could imagine it hitting a data store of some type.
private ContactDto CreateSampleData()
        {
            var data = new ContactDto();
            data.Email = "foreachdev@gmail.com";
            data.FullName = "Joshua Kincaid";
            data.AddressCollection.AddRange(new[]{
             new AddressDto(){
                 Line1="Admissions",
                 Line2="1 Old Dominion Way",
                 City="Norfolk",
                 ProvinceState="Virginia",
                 PostalCode="23529",
                 PreferenceOrder=5
             },
             new AddressDto(){
                 Line1="Handsome Biscuit",
                 Line2="2511 Colonial Ave",
                 City="Norfolk",
                 ProvinceState="Virginia",
                 PostalCode="23517",
                 PreferenceOrder=1
             },
             new AddressDto()
             {
                 Line1="OTTO",
                 Line2="1 5th Ave",
                 City="Manhattan",
                 ProvinceState="New York",
                 PostalCode="10003",
                 PreferenceOrder=0
             }
            });
            return data;
        }
XmlDataContractSerializer uses a base class to allow us to easily use an IOC Container to change out serializers with a config change.
public class XmlDataContractSerializer<T> : XmlSerializer<T, DataContractSerializerAdapter<T>> { }

The Rest of the Code 

XmlSerializer exposes the ISerializer interface for dependency injection purposes. You could have a IXmlSerializer or include a parameter for format if that is your preference.
using System;
using System.IO;
using System.Text;
using System.Xml;

namespace Jrf.Core
{
    /// <summary>
    /// A Serializer component for working with POCOs that has DataContract and DataMember classes. 
    /// </summary>
    public class XmlSerializer<T, U> : ISerializer<T> where U : ISerializer<T>, new()
    {
        private const string _XmlDeclaration = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
        /// <summary>
        /// Serializes class with DataContract and DataMember attributs to Xml using an in memory stream then reads it to a string.
        /// </summary>
        /// <typeparam name="T">Type to serialize.</typeparam>
        /// <param name="dataObject">The object to serialize.</param>
        /// <returns>String containing xml form of the POCO.</returns>
        public string SerializeToXmlString(T dataObject, bool includeDeclaration = false)
        {
            var output = new StringBuilder();
            var stream = new MemoryStream();
            var reader = new StreamReader(stream);
            try
            {
                Serialize(dataObject, stream);
                if (includeDeclaration)
                {
                    output.Append(_XmlDeclaration);
                    output.Append(Environment.NewLine);
                }
                output.Append(reader.ReadToEnd());
            }
            finally
            {
                //Avoids multiple dispose calls. (See FXCOP CA2202)
                if (reader != null)
                {
                    reader.Dispose();
                }
            }
            return output.ToString();
        }
        /// <summary>
        /// Serializes class with DataContract and DataMember attributs to XmlDocument using an in memory stream.
        /// </summary>
        /// <typeparam name="T">Type to serialize.</typeparam>
        /// <param name="dataObject">The object to serialize.</param>
        /// <returns>XmlDocument.</returns>
        public XmlDocument SerializeToXmlDocument(T dataObject, bool includeDeclaration = false)
        {
            var doc = new XmlDocument();
            using (var stream = new MemoryStream())
            {
                Serialize(dataObject, stream);
                doc.Load(stream);
                if (includeDeclaration)
                {
                    var root = doc.DocumentElement;
                    var dec = doc.CreateXmlDeclaration("1.0", "UTF-8", null);
                    doc.InsertBefore(dec, root);
                }
            }
            return doc;
        }
        /// <summary>
        /// Creates and runs the serializer.
        /// </summary>
        /// <typeparam name="T">Type of Object to serialize.</typeparam>
        /// <param name="dataObject">Object to serialize.</param>
        /// <param name="stream">Stream to dump the serilization to.</param>
        public void Serialize(T dataObject, Stream stream)
        {
            var serializer = new U();
            serializer.Serialize(dataObject, stream);
            stream.Position = 0;
        }
    }
}
Finally, we have the simple DataContractSerializer strategy.
using System.IO;
using System.Runtime.Serialization;

namespace Jrf.Core
{
    /// <summary>
    /// Provides Serializer Services via the DataContract Serializer.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DataContractSerializerAdapter<T> : ISerializer<T>
    {
        /// <summary>
        ///  Serializer method.
        /// </summary>
        /// <typeparam name="T">Type of object to be serialized.</typeparam>
        /// <param name="input">Object to be serialized.</param>
        /// <param name="output">Xml stream.</param>
        public void Serialize(T input, Stream output)
        {
            var serializer = new DataContractSerializer(typeof(T));
            serializer.WriteObject(output, input);
        }
    }
}

Wrapping up

 I will upload my code to Github as I continue this series. In the mean time, if I forgot any of the code in the walk through, please let me know.

No comments:

Post a Comment