January 2011 - Posts

Xml Serialization and the [Obsolete] Attribute

I learned something new today: Starting with .NET 3.5, the XmlSerializer no longer serializes properties that are marked with the Obsolete attribute.  I can’t say that I really agree with this.  Marking something Obsolete is supposed to be something for a developer to deal with in source code.  Once an object is serialized to XML, it becomes data.  I think using the Obsolete attribute as both a compiler flag as well as controlling XML serialization is a bad idea.

In this post, I’ll show you how I ran into this and how I got around it.

The Setup

Let’s start with some make-believe code to demonstrate the issue.  We have a simple data class for storing some information.  We use XML serialization to read and write the data:

public class MyData
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public List<String> Hobbies { get; set; }
 
    public MyData()
    {
        this.Hobbies = new List<string>();
    }
}

Now a few simple lines of code to serialize it to XML:

static void Main(string[] args)
{
    var data = new MyData
                   {
   FirstName = "Zachary", 
                    LastName = "Smith", 
                    Age = 50, 
                    Hobbies = {"Mischief", "Sabotage"},
                   };
    var serializer = new XmlSerializer(typeof (MyData));
    serializer.Serialize(Console.Out, data);
    Console.ReadKey();
}

And this is what we see on the console:

<?xml version="1.0" encoding="IBM437"?>
<MyData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Age>50</Age>
  <FirstName>Zachary</FirstName>
  <LastName>Smith</LastName>
  <Hobbies>
    <string>Mischief</string>
    <string>Sabotage</string>
  </Hobbies>
</MyData>

 

The Change

So we decided to track the hobbies as a list of strings.  As always, things change and we have more information we need to store per-hobby.  We create a custom “Hobby” object, add a List<Hobby> to our MyData class and we obsolete the old “Hobbies” list to let developers know they shouldn’t use it going forward:

public class Hobby
{
    public string Name { get; set; }
    public int Frequency { get; set; }
    public int TimesCaught { get; set; }
 
    public override string ToString()
    {
        return this.Name;
    }
}
public class MyData
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    [Obsolete("Use HobbyData collection instead.")]
    public List<String> Hobbies { get; set; }
    public List<Hobby> HobbyData { get; set; }
 
    public MyData()
    {
        this.Hobbies = new List<string>();
        this.HobbyData = new List<Hobby>();
    }
}

Here’s the kicker: This serialization is done in another application.  The consumers of the XML will be older clients (clients that expect only a “Hobbies” collection) as well as newer clients (that support the new “HobbyData” collection).  This really shouldn’t be a problem – the obsolete attribute is metadata for .NET compilers.  Unfortunately, the XmlSerializer also looks at the compiler attribute to determine what items to serialize/deserialize.  Here’s an example of our problem:

static void Main(string[] args)
{
    var xml = @"<?xml version=""1.0"" encoding=""IBM437""?>
<MyData xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Age>50</Age>
<FirstName>Zachary</FirstName>
<LastName>Smith</LastName>
<Hobbies>
<string>Mischief</string>
<string>Sabotage</string>
</Hobbies>
</MyData>";
    var serializer = new XmlSerializer(typeof(MyData));
    var stream = new StringReader(xml);
    var data = (MyData) serializer.Deserialize(stream);
 
    if( data.Hobbies.Count != 2)
    {
        throw new ApplicationException("Hobbies did not deserialize properly");
    }
}

If you run the code above, you’ll hit the exception.  Even though the XML contains a “<Hobbies>” node, the obsolete attribute prevents the node from being processed.  This will break old clients that use the new library, but don’t yet access the HobbyData collection.

The Fix

This fix (in this case), isn’t too painful.  The XmlSerializer exposes events for times when it runs into items (Elements, Attributes, Nodes, etc…) it doesn’t know what to do with.  We can hook in to those events and check and see if we’re getting something that we want to support (like our “Hobbies” node).

Here’s a way to read in the old XML data with full support of the new data structure (and keeping the Hobbies collection marked as obsolete):

static void Main(string[] args)
{
    var xml = @"<?xml version=""1.0"" encoding=""IBM437""?>
<MyData xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
<Age>50</Age>
<FirstName>Zachary</FirstName>
<LastName>Smith</LastName>
<Hobbies>
<string>Mischief</string>
<string>Sabotage</string>
</Hobbies>
</MyData>";
    var serializer = new XmlSerializer(typeof(MyData));
    serializer.UnknownElement += serializer_UnknownElement;
    var stream = new StringReader(xml);
    var data = (MyData)serializer.Deserialize(stream);
 
    if (data.Hobbies.Count != 2)
    {
        throw new ApplicationException("Hobbies did not deserialize properly");
    }
}
 
static void serializer_UnknownElement(object sender, XmlElementEventArgs e)
{
    if( e.Element.Name != "Hobbies")
    {
        return;
    }
 
    var target = (MyData) e.ObjectBeingDeserialized;
    foreach(XmlElement hobby in e.Element.ChildNodes)
    {
        target.Hobbies.Add(hobby.InnerText);
        target.HobbyData.Add(new Hobby{Name = hobby.InnerText});
    }
}

As you can see, we hook in to the “UnknownElement” event.  Once we determine it’s our “Hobbies” node, we deserialize it ourselves – as well as populating the new HobbyData collection.  In this case, we have a fairly simple solution to a small change in XML layout.  If you make more extensive changes, it would probably be easier to do some custom serialization to support older data.

A sample project with all of this code is available from my repository on bitbucket.

Technorati Tags: ,,
Posted by PSteele | 1 comment(s)
Filed under:

Welcome 2011

About this time last year, I wrote a blog post about how January of 2010 was almost over and I hadn’t done a single blog post.  Ugh…  History repeats itself.

2010 in Review

If I look back at 2010, it was a great year in terms of technology and development:

  • Visited Redmond to attend the MVP Summit in February.  Had a great time with the MS product teams and got to connect with some really smart people.
  • Continued my work on Visual Studio Magazine’s “C# Corner” column.  About mid-year, the column changed from an every-other-month print column to an every-other-month print column along with bi-monthly web-only articles.  Needless to say, this kept me even busier and away from my blog.
  • Participated in another GiveCamp!  Thanks to the wonderful leadership of Michael Eaton and all of his minions, GiveCamp 2010 was another great success.  Planning for GiveCamp 2011 will be starting soon…
  • I switched to DVCS full time.  After years of being a loyal SVN user, I got bit by the DVCS bug.  I played around with both Mercurial and Git and finally settled on Mercurial.  It’s seamless integration with Windows Explorer along with it’s wealth of plugins made me fall in love.  I can’t imagine going back and using a centralized version control system.
  • Continued to work with the awesome group of talent at SRT Solutions.  Very proud that SRT won it’s third consecutive FastTrack award!
  • Jumped off the BlackBerry train and enjoying the smooth ride of Android.  It was time to replace the old BlackBerry Storm so I did some research and settled on the Motorola DroidX.  I couldn’t be happier.  Android is a slick OS and the DroidX is a sweet piece of hardware.  Been dabbling in some Android development with both Eclipse and IntelliJ IDEA (I like IntelliJ IDEA a lot better!).

 

2011 Plans

On January 1st I was pleasantly surprised to get an email from the Microsoft MVP program letting me know that I had received the MVP award again for my community work in 2010.  I’m honored and humbled to be recognized by Microsoft as well as my peers!

I’ll continue to do some Android development.  I’m currently working on a simple app to get me feet wet.  It may even makes it’s way into the Android Market.

I’ve got a project that could really benefit from WPF so I’ll be diving into WPF this year.  I’ve played around with WPF a bit in the past – simple demos and learning exercises – but this will give me a chance to build an entire application in WPF.  I’m looking forward to the increased freedom that a WPF UI should give me.

I plan on blogging a lot more in 2011!

Technorati Tags: ,,,,,
Posted by PSteele | with no comments
More Posts