The Rest Starter Kit and XML Parsing

May 18th, 2010Posted by Steve

 

"A Bridge Between XML and c#" I learned early on my programming career you can do things the hard way or the smart way. Writing XML parsers is hard to get right there has to be an easier way. Fortunately Microsoft came out with a nice little utility that comes with Visual Studio that makes things easier xsd.exe. BridgeWhat this will do is transform almost any xml into a bunch of classes representing the XML the slight disadvantage of this is, that the utility is a command line stand alone application.What I wanted was something that runs from Visual Studio at the click of a button. Microsoft's Rest Starter Kit includes exactly this tool and is installed into Visual Studio automatically. After installation a new menu option is added to the Edit menu "Paste XML as Types", This little tool opens up a world of possibilities to the innovative programmer to create a very effective "bridge" between XML and .Net languages. The output is not perfect however, it will work 100% of the time but there are one or two improvements that can be made to the output code to make your life easier. You will however need to familiarize yourself with XML serialization in .Net which can be a little off putting but at the root of it is extremely simple, so fire up visual studio and create a simple XML document.

<?xml version="1.0" encoding="utf-8" ?>
<library>
  <shelf id="fiction">
    <book>
      <title>Of Mice and Men</title>
      <author>John Steinbeck</author>
    </book>
    <book>
      <title>Harry Potter and the Philosopher's Stone</title>
      <author>J.K. Rowling</author>
    </book>
  </shelf>
</library>

Now copy this code to the clipboard create a class with a suitable namespace and use the new menu item "Paste as XML Types" and you will end up with this plus a few other classes.

After cleaning up the code yout will now see there is a big difference.

using System.Xml.Serialization;
using System.Collections.Generic;

namespace smith
{
    [XmlType(AnonymousType = true)]
    [XmlRoot(Namespace = "library", IsNullable = false)]
    public partial class Library
    {
        public Shelf Shelf
        { get; set; }
    }

    [XmlType(AnonymousType = true)]
    public partial class Shelf
    {
        private List<Book> book = new List<Book>();
        [XmlElement("book")]
        public List<Book> Book
        {
            get { return this.book; }
            set { this.book = value; }
        }

        [XmlAttribute("id")]
        public string Id
        { get; set; }
    }

    [XmlType(AnonymousType = true)]
    public partial class Book
    {
        [XmlElement("title")]
        public string Title
        { get; set; }

        [XmlElement("author")]
        public string Author
        { get; set; }
    }
}

Some of the changes are purely cosmetic, for example changing the Case of the Fields and classes to a more conventional Microsoft Pattern. The input and output XML are exactly the same the only difference is when you are using the class its properties are much clearer.Instead of using arrays we have changed them to the generic pattern of List ther serializer takes care of the type. The reason for this is that there is no need to set the size of the array and you will see that the private internal field which was an array initializes to a new List<> item.

We now have the basics of an engine to parse XML, but still missing one or two essential items such as loading and outputting XML document from and to the parser. For this we can use the XMLSerializer and we can put the code into the class itself so that it is entirely self constained.

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

namespace smith
{
    /// <summary>
    /// The Root Class
    /// </summary>
    /// <remarks>
    /// Note the XmlRoot Attribute, the programmer sees the class name as "Library"
    /// however the output and input see it as "library" in lower case
    </remarks>
    [XmlType(AnonymousType = true)]
    [XmlRoot(Namespace = "library", IsNullable = false)]
    public partial class Library
    {
        public Shelf Shelf
        { get; set; }

        /// <summary>
        /// Output for XML
        /// </summary>
        /// <remarks>The XMLIgnore tag tells the serializer
        /// not to include it in any output
        </remarks>
        [XmlIgnore]
        public string Xml
        {
            get
            {
                string buffer = string.Empty;
                using (MemoryStream stream = new MemoryStream())
                {
                    XmlSerializer serializer = new XmlSerializer(typeof(Library));
                    serializer.Serialize(stream, this);
                    buffer = Encoding.UTF8.GetString(stream.ToArray());
                    stream.Close();
                }
                return buffer;
            }
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        /// <remarks>Needed for Serializer</remarks>
        public Library()
        {
        }

        /// <summary>
        /// Overloaded Constructor
        /// </summary>
        /// <param name="filepath">File Path</param>
        public Library(string filePath)
        {
            using (XmlTextReader reader = new XmlTextReader(filePath))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(Library));
                Library buffer = (Library)serializer.Deserialize(reader);
                this.Shelf = buffer.Shelf;
                reader.Close();
            }
        }
    }

    [XmlType(AnonymousType = true)]
    public partial class Shelf
    {
        private List<Book> books = new List<Book>();
        [XmlElement("book")]
        public List<Book> Books
        {
            get { return this.books; }
            set { this.books = value; }
        }

        [XmlAttribute("id")]
        public string Id
        { get; set; }
    }

    [XmlType(AnonymousType = true)]
    public partial class Book
    {
        [XmlElement("title")]
        public string Title
        { get; set; }

        [XmlElement("author")]
        public string Author
        { get; set; }
    }
}

So thats it now you have made a custom class with very little effort that will read and load XML and also produce XML. Effectively a custom XML parser and generator all in one without the hassle of parsing nodes. Using it could not be easier here is a quick piece of code to show how.

public void ReadXml()
{
    Library library = new Library("books.xml");
    foreach (Book book in library.Shelf.Books)
    {
        Console.WriteLine("Title" + book.Title);
        Console.WriteLine("Author " + book.Author);
    }

    Book newbook = new Book();
    newbook.Title = "War and Peace";
    newbook.Author = "Tolstoy";
    library.Shelf.Books.Add(newbook);

    // Write the XML to the console
    Console.WriteLine(library.Xml);
}