tsJensen

A quest for software excellence...

Simple XML to DTO and Back Again

Data transfer objects, especially in this disconnected world we live in, have become quite popular and highly useful. Here's one way I've found to create XML file based DTOs with quick and easy loading and saving from and back to the original XML format. All you need is a schema and a little gem from thinktecture.

A couple of months ago I began working on a project that would require reading and modifying a number of XML based configuration files. I had previously used thinktecture's Web Services Contract First (WSCF) plugin for Visual Studio created by Christian Weyer and Buddhike de Silva to create contract-first web services. At the time I had discovered how convenient it was to use the web service client code generation to generate simple DTO object code as well, but it had to be done in a clever way to allow me to throw away the web service code and just use the DTO object code.

A quick visit to thinktecture's web site and I found that version 0.7 had just been released on October 25, 2006. And to my delight, the major new feature in version 0.7 was the ability to generate data transfer object or data contract code from the UI plugin for Visual Studio 2005. Just install WSCF version 0.7 and then in Visual Studio 2005 create a schema file like the one below. Then right click the XSD file in the solution explorer and select "Generate Data Contract Code" from the menu. A simple dialog like this one pops up.

This will produce a partial class and give you everything you need except a Load and Save method to store the DTO in XML format. It was a simple matter to add the following partial class and the static support class called Serializer as shown below. If you need something like this, I hope this helps.

partial class:

    public partial class Parser
    {
        private static string targetNamespace = "http://mynamespace/v1/parser.xsd";
        public static Parser Load(string fileName)
        {
            if (fileName == null) return null;
            return (Parser)Serializer.Load(fileName, typeof(MyNamespace.Parser), Parser.targetNamespace);
        }
        public void Save(string fileName)
        {
            Serializer.Save(fileName, this, this.GetType(), Parser.targetNamespace);
        }
    }

Serializer class:

    public static class Serializer
    {
        private static XmlSerializerNamespaces GetNamespaces(string targetNamespace)
        {
            XmlSerializerNamespaces ns;
            ns = new XmlSerializerNamespaces();
            ns.Add("", targetNamespace);
            ns.Add("xs", "http://www.w3.org/2001/XMLSchema");
            return ns;
        }

        public static object Load(string fileName, System.Type objType, string targetNamespace)
        {
            string xml = File.ReadAllText(fileName);
            object obj = Serializer.FromXml(xml, objType, targetNamespace);
            return obj;
        }

        public static void Save(string fileName, object obj, System.Type objType, string targetNamespace)
        {
            string xml = Serializer.ToXml(obj, objType, targetNamespace);
            File.WriteAllText(fileName, xml);
        }

        public static string ToXml(object obj, System.Type objType, string targetNamespace)
        {
            XmlSerializer ser;
            ser = new XmlSerializer(objType, targetNamespace);
            MemoryStream memStream;
            memStream = new MemoryStream();
            XmlTextWriter xmlWriter;
            xmlWriter = new XmlTextWriter(memStream, Encoding.UTF8);
            xmlWriter.Formatting = Formatting.Indented;
            xmlWriter.Indentation = 1;
            xmlWriter.IndentChar = '\t';
            xmlWriter.Namespaces = true;
            ser.Serialize(xmlWriter, obj, Serializer.GetNamespaces(targetNamespace));
            xmlWriter.Close();
            memStream.Close();
            string xml;
            xml = Encoding.UTF8.GetString(memStream.GetBuffer());
            xml = xml.Substring(xml.IndexOf('<'));
            xml = xml.Substring(0, (xml.LastIndexOf('>') + 1));
            return xml;
        }

        public static object FromXml(string xml, System.Type objType, string targetNamespace)
        {
            XmlSerializer ser;
            ser = new XmlSerializer(objType, targetNamespace);
            StringReader stringReader;
            stringReader = new StringReader(xml);
            XmlTextReader xmlReader;
            xmlReader = new XmlTextReader(stringReader);
            object obj;
            obj = ser.Deserialize(xmlReader);
            xmlReader.Close();
            stringReader.Close();
            return obj;
        }
    }

example schema:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="parser"
             targetNamespace="http://mynamespace/v1/parser.xsd"
             elementFormDefault="qualified"
             xmlns="http://mynamespace/v1/parser.xsd"
             xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Parser">
        <xs:complexType>
            <xs:all>
                <xs:element ref="ColumnWidths" minOccurs="0" maxOccurs="1" />
                <xs:element name="ExpectedColumnCount" type="xs:int" />
                <xs:element name="MaxBufferSize" type="xs:int" />
                <xs:element name="MaxRows" type="xs:int" />
                <xs:element name="SkipDataRows" type="xs:int" />
                <xs:element name="FirstRowHasHeader" type="xs:boolean" />
                <xs:element name="TrimResults" type="xs:boolean" />
                <xs:element name="IncludeFileLineNumber" type="xs:boolean" />
                <xs:element name="FixedWidth" type="xs:boolean" />
                <xs:element name="RowDelimiter" type="xs:string" />
                <xs:element name="ColumnDelimiter" type="xs:string" />
                <xs:element name="TextQualifier" type="xs:int" />
                <xs:element name="EscapeCharacter" type="xs:int" />
                <xs:element name="CommentCharacter" type="xs:int" />
            </xs:all>
        </xs:complexType>
    </xs:element>
    <xs:element name="ColumnWidths">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="ColumnWidth" minOccurs="0" maxOccurs="unbounded" type="xs:int" />
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>