Confessions of a code junkie
 
Tuesday, February 12, 2008

Put Down the XmlNode and Step Away From the StringBuilder
 
I'm sure there are plenty of you out there who have your boss hankering to use some of that "Xml Stuff". Sometimes we get sheltered in our own little world and haven't actually had to work with a whole lot of xml data that wasn't already wrapped up by the Project Settings object created by Visual Studio. So naturally, you'd probably go to the "tubes" and search for some additional information. Undoubtedly, you will run across pages telling you how to construct some Xml in C# by creating a new XmlDocument and adding XmlNode children which will have attributes and subnodes and namespaces, ad infinitum. And you'll realize you now have some of the world's fugliest code. So you decide, "oh, well xml is really similar to html, I'll just build it using a StringBuilder" and you end up with slightly less fugly code, that is until you try to read it back in.

These methods are completely unnecessary. PUT DOWN THE XMLNODE AND STEP AWAY FROM THE STRINGBUILDER. Slowly move your hand to the mouse and scroll down to read about the easiest way to write and read Xml in the .net framework.

Ok, quick show of hands: who thinks working with simple .net business model objects is brain dead easy? Alright, if your hand isn't up, you are beyond hope, please leave now. Of course, working with simple objects is about the simplest exercise for code next to "Hello World". If you can create a .net object that models the xml you want to store, you are 95% done with outputting well formed Xml.

Example: If I wanted to store information about a few people and their pets I would create three classes:
public class PetClub
{
public List<Person> Members { get; set; }
}

public class Person
{
public string Name { get; set; }
public List<Pet> Pets { get; set; }
}

public class Pet
{
public string Name { get; set; }
public string Type { get; set; }
}
To get an Xml representation of an instance of PetClub, all you have to do is use the XmlSerializer found in the System.Xml.Serialization namespace. I've wrapped up all the necessary code into a short utility method.
public static string ConvertToXml(object item)
{
XmlSerializer xmlser = new XmlSerializer(item.GetType());
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
xmlser.Serialize(ms, item);
UTF8Encoding textconverter = new UTF8Encoding();
return textconverter.GetString(ms.ToArray());
}
}

Simply calling: ConvertToXml(myPetClubInstance) will spit back the following xml:

<PetClub xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Members>
    <Person>
      <Name>Jon</Name>
      <Pets>
        <Pet>
          <Name>Chester</Name>
          <Type>Savannah Cat</Type>
        </Pet>
        <Pet>
          <Name>Abby</Name>
          <Type>Domestic Miniature Panther</Type>
        </Pet>
      </Pets>
    </Person>
    <Person>
      <Name>Dan</Name>
      <Pets>
        <Pet>
          <Name>Lucy</Name>
          <Type>Semi-sweet Chocolate Lab</Type>
        </Pet>
      </Pets>
    </Person>
  </Members>
</PetClub>


At this point you might be saying: "That is cool and all, Jon, but this looks like a lot of xml to be outputting for such little actual data. And what if I want to have more control over xml element naming". Dear friend, let not your heart be troubled for there is help on the way. By adding some simple Property Attributes to our classes we can completely change how our xml is constructed.  We can have the object in whatever form is most convienient for our program while still allowing us to interoperate with an xml document created by a different application with a different idea of what good names are.

Changing our two classes to:
public class Person
{
[XmlElement("FirstName")]
public string Name { get; set; }
public List<Pet> Pets { get; set; }
}

public class Pet
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlAttribute("Breed")]
public string Type { get; set; }
}

Will output this xml:

<PetClub xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Members>
    <Person>
      <FirstName>Jon</FirstName>
      <Pets>
        <Pet Name="Chester" Breed="Savannah Cat" />
        <Pet Name="Abby" Breed="Domestic Miniature Panther" />
      </Pets>
    </Person>
    <Person>
      <FirstName>Dan</FirstName>
      <Pets>
        <Pet Name="Lucy" Breed="Semi-sweet Chocolate Lab" />
      </Pets>
    </Person>
  </Members>
</PetClub>



Pretty simple, huh? Can you imagine how difficult it would be to make these changes via XmlNodes or a StringBuilder? There are about 10 other property attributes available for xml serialization, including the very useful XmlIgnore() attribute which makes the appropriate property not serialized. I'll leave the rest of the research up to you.

And I almost forgot, to read your xml back into an object use the following method:

public static T FromXml<T>(string xml)
{
XmlSerializer xmlser = new XmlSerializer(typeof(T));
using (System.IO.StringReader sr = new System.IO.StringReader(xml))
{
return (T)xmlser.Deserialize(sr);
}
}

Sample Code - XmlSerializerSample.cs (2.33 KB)


Tuesday, February 12, 2008 - 8:29 AM CST - Permalink kick it on DotNetKicks.com   Comments [7] -
Tags: Tips and Tricks


Tuesday, February 12, 2008 11:45:28 AM (Central Standard Time, UTC-06:00)
What about the opposite, where you need to read content from an xml file? Is there a similar approach possible, where you populate your class/collection from an xml files content?
dnc
Tuesday, February 12, 2008 12:05:41 PM (Central Standard Time, UTC-06:00)
@dnc, there definitely is, the code is the very last thing in the post:

public static T FromXml<T>(string xml)
{
XmlSerializer xmlser = new XmlSerializer(typeof(T));
using (System.IO.StringReader sr = new System.IO.StringReader(xml))
{
return (T)xmlser.Deserialize(sr);
}
}

Usage looks like:

PetClub myClub = FromXml<PetClub>([insert your xml as a string]);



Tuesday, February 12, 2008 12:08:27 PM (Central Standard Time, UTC-06:00)
@dnc: I'm reading your comment 2 different ways, so here are two different answers.

1. If you have the classes already, your fields/collections will automatically be populated with all the data in the XML file when you deserialize.

2. If you have data but no classes, check out the xsd.exe command line utility that ships with Visual Studio. You feed it an XML file to generate an XSD file, then feed it that XSD file to generate a class file. Now you can serialize and deserialize your data to and from the XML file.

Hope that helps. -Tim
Tim B
Tuesday, February 12, 2008 12:15:22 PM (Central Standard Time, UTC-06:00)
Good article, but the source code download link is broken:

Server Error in '/' Application.
This type of page is not served.
Description: The type of page you have requested is not served because it has been explicitly forbidden. The extension '.cs' may be incorrect. Please review the URL below and make sure that it is spelled correctly.
Tuesday, February 12, 2008 12:20:50 PM (Central Standard Time, UTC-06:00)
@jrummel

Thanks for the heads up, the link should be fixed now
Tuesday, February 12, 2008 12:47:35 PM (Central Standard Time, UTC-06:00)
One thing that is a pain is that this solution won't work if you've got a Dictionary<K,V> in your class, due to dictionaries breaking XML serialization.

Nice solution overall, though.
Tuesday, February 12, 2008 1:00:15 PM (Central Standard Time, UTC-06:00)
@Judah That's true but you can get around that to some degree if you implement IXmlSerializable and handle generating and reading your own XML.

Granted, that defeats the whole point of the original post.. but it does provide a way to serialize your objects even if they contain properties that implement IDictionary.
Comments are closed.
 


All Content © 2009, Jon von Gillern
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.


 


Tags

Archive

Blogroll