Sponsored Links


Resources

.NET Research Library
Get .NET related white papers, case studies and webcasts

TSS Featured Entry: Strongly-typed event-rising design-time generated custom XmlSerializersTSS Featured Entry: Strongly-typed event-rising design-time generated custom XmlSerializersTSS Featured Entry: Strongly-typed event-rising design-time generated custom XmlSerializers Discuss Discuss Discuss Printer friendly Printer friendly Printer friendly Direct to Blog Entry Direct to Blog Entry Direct to Blog Entry

Introduction

There are a couple known issues with the XmlSerializer:

  1. First-time use is painfully slow, because of the temporary assembly generation and compilation.
  2. If you want to do something else other than straight serialization, there's no easy way of customizing it.

Whidbey will bring a solution to the first, in the form of the "sgen" tool, which will generate at design-time the serialization assembly. I still haven't looked at the solutions it offers for the second issue.

So, are you condemned to waiting 'till Whidbey? Are you condemned to either choose between the straightforward-but-almost-impossible-to-customize XmlSerializer approach and endless lines of XmlReader/XmlDocument/XPath code to do almost the same yet generally with less performance just to gain the flexibility you need? Well, I turns out that I am a firmly believer on automating boring and repetitive tasks, so I'm not happy at all with people choosing the later, because I believe it leads to hard to maintain, inflexible, and really ugly code. You waste so much effort that would be better put to work on creative stuff, and making your app great. And performance is always a big issue, at least in my view.

This article explains how to achieve design-time XmlSerializer generation, how it's customized to allow for the stuff you always thought impossible, such as getting events for each kind of object being deserialized.

Recall that I wrote this code in 4 hours as I waited for my plane to Redmond on the airport :o), so it's not the most gorgeous piece of code you'll see coming from me, but it certainly gets a pretty cool job done!

Near the end of a previous article I said it was not possible to customize the XmlSerializer to use something else other than the built-in dynamically generated code. I was wrong. What follows is an explanation of how I achieved what seemed impossible.

Usage

Right now, this is a command-line utility. Its arguments are:

SGen.exe fullTypeName assemblyFile targetNamespace outputFileName

First argument is the namespace-qualified name of the type you'll be using with the XmlSerializer. The second argument is the file name of the assembly containing the type. Next follows the namespace you want to put the generated code into. And finally you can specify the file name to write the generated code to.

The output of the tool is a set of classes you can use for XML serialization, which not only allows you to avoid the run-time impact of temporary assembly generation, but also allows you to attach to events that are exposed for each element that will be deserialized. If you had a class called Order like the following (presumably generated from an XSD):

public class Order
{
  // Members plus optional XML serialization attributes
}

You would get the following classes:

  • OrderReader: a class inheriting from the XmlSerializationReader-derived class generated by the XmlSerializer, also included in the file but as an inner class of the OrderSerializer.
  • OrderWriter: a class inheriting from the XmlSerializationWriter-derived class generated by the XmlSerializer, also included in the file but as an inner class of the OrderSerializer too.
  • OrderSerializer: XmlSerializer-derived class that allows you to pass the two previous custom classes for serialization, in order to skip the dynamic code generation.
  • OrderDeserializedHandler: handler for an event exposed by the OrderReader, called OrderDeserialized, which you can attach to in order to perform additional processing when deserialization is done.

The custom serializer is generated basically to allow for the custom reader/writer classes to be passed-in, as the XmlSerializer class itself doesn't allow for this, but provides the hook methods CreateReader and CreateWriter, as well Serialize and Deserialize overloads receiving the result of those method calls. So in order to deserialize an order class, you instantiate the reader, pass it to the custom serializer, and call Deserialize as usual:

// Create the typed reader.
OrderReader or = new OrderReader();

// Create custom serializer receiving the custom reader.
OrderSerializer os = new OrderSerializer(or);

// Deserialize as usual
object order = os.Deserialize(inputReader);

The same process would be done for serializing the object. At this point you already saved a *huge* amount of processing for the initial hit on this class upon deserialization. And this impact is higher as the object to deserialize is more complex.

But there's more to this generation process than just performance boost. Let's say the Order class, among other properties, has one of type Customer, and then a collection of Items. The custom reader would expose an event for each of them, so you can perform additional processing. So you could do the following:

public void Test()
{
  XmlTextReader tr = new XmlTextReader(GetInputStream());
  // Typed reader.
  OrderReader or = new OrderReader();

  // Attach to events for each object!
  reader.CustomerDeserialized += new CustomerDeserializedHandler(OnCustomerDeserialized);
  reader.OrderDeserialized += new OrderDeserializedHandler(OnOrderDeserialized);
  reader.ItemDeserialized += new ItemDeserializedHandler(OnItemDeserialized);

  // Custom serializer receiving the custom reader.
  OrderSerializer os = new OrderSerializer(or);

  // Deserialize as usual, but all event handlers called while deserializing!
  object order = os.Deserialize(tr);

  // Do something with the order...
}

private void OnCustomerDeserialized(Customer customer)
{
	Console.WriteLine(customer.FirstName);
}

private void OnOrderDeserialized(Order order)
{
	Console.WriteLine(order.Id);
}

private void OnItemDeserialized(Item item)
{
	Console.WriteLine(item.Price);
}

You can even use this event callbacks as a more programmer-friendly approach to XML processing than the lower-level XmlReader. You just have to create the XSD, generate classes with xsd.exe or something better, and use the SGen generated reader. You just attach to the events for each element you're interested in, and process it using friendly properties/fields instead of XmlReader.Value, XmlReader.GetAttribute and the like.

You can easily set this tool to run as a post-build event, by setting appropriate project field to:

..\..\..\SGen\bin\Debug\SGen SGen.Tests.Order SGen.Tests.dll SGen.Tests.Serialization ..\..\OrderSerialization.cs

Implementation

The code generated by the XmlSerializer can be kept around by using the technique explained in a previous post. Using that approach, the SGen utility instantiates an XmlSerializer passing the type you specify as arguments. After that, using hacky reflection, it retrieves the temporary code location burned deep inside the XmlSerializer private members and internal classes. Afterwards, using a mix of CodeDom and raw string manipulation, the final code is generated. I've done quite a bit of regular expressions-based parsing too, and there's a region called "Code templates" that provide the skeleton for the generation process.

I didn't create everything using CodeDom because the XmlSerializer generates just C# output, therefore, there wasn't much benefit in trying to do everything the "right" way. The code and the regular expressions are pretty nasty for the non-accustomed eye, so I'll save you the trouble and instead point you to the respective code download.

What's worth seeing is how the XmlSerializer is being extended:

/// <summary>Custom serializer for Order type.</summary>
public class OrderSerializer : System.Xml.Serialization.XmlSerializer
{
  OrderReader _reader;
  OrderWriter _writer;

  /// <summary>Constructs the serializer with a pre-built reader.</summary>
  public OrderSerializer(OrderReader reader)
  {
    _reader = reader;
  }

  /// <summary>Constructs the serializer with a pre-writer reader.</summary>
  public OrderSerializer(OrderWriter writer)
  {
    _writer = writer;
  }

  /// <summary>Constructs the serializer with pre-built reader and writer.</summary>
  public OrderSerializer(OrderReader reader, OrderWriter writer)
  {
    _reader = reader;
    _writer = writer;
  }

  /// <summary>See <see cref="XmlSerializer.CreateReader"/>.</summary>
  protected override XmlSerializationReader CreateReader()
  {
    return _reader;
  }


  /// <summary>See <see cref="XmlSerializer.CreateWriter"/>.</summary>
  protected override XmlSerializationWriter CreateWriter()
  {
    return _writer;
  }

  /// <summary>See <see cref="XmlSerializer.Deserialize"/>.</summary>
  protected override object Deserialize(XmlSerializationReader reader)
  {
    if (!(reader is OrderReader))
      throw new ArgumentException("reader");

    return ((OrderReader)reader).Read();
  }

  /// <summary>See <see cref="XmlSerializer.Serialize"/>.</summary>
  protected override void Serialize(object o, XmlSerializationWriter writer)
  {
    if (!(writer is OrderWriter))
      throw new ArgumentException("writer");

    ((OrderWriter)writer).Write((SGen.Tests.Order)o);
  }

  // Inner XmlSerializer-generated reader and writer classes
}

So it *was* extensible in the end, right? It's a little bit cumbersome, but it's possible to extend it as you can see.

Don't forget to download the tool and play with it!



About the author

Daniel Cazzulino kzu@aspnet2.com
Blog: http://clariusconsulting.net/blogs/kzu/

Daniel Cazzulino (a.k.a. kzu) lives in Buenos Aires, Argentina, and is a senior architect, developer, and cofounder of Clarius Consulting S.A. He has coauthored several books on Web development and server controls with ASP.NET, written and reviewed many articles for ASP Today and C# Today, and currently enjoys sharing his .NET and XML experiences through his blog at http://clariusconsulting.net/blogs/kzu/.

News | Blogs | Discussions | Tech talks | White Papers | Downloads | Articles | Media kit | About
All Content Copyright ©2007 TheServerSide Privacy Policy
Site Map