XAML Serialization FTW

Foreground

I wanted to record some of the things I learned recently about XAML serialization of generic dictionaries.  This subject is of interest to anybody who is interested in creating FrameworkElement objects that expose dictionary collections or those, like me, who are exploring the use of XAML serialization as an alternative for XML or other string-based serialization methods.

[more]

Background

A little background… My project at work is at the stage where its the last responsible moment to make architecture decisions about the designer.  For us, that means using Visual Studio Shell to host our designer package (unless a deal breaker crops up).

So I examined ways that the design workflow would be handled, from currently executing server to the designer, in the editing environment, and into some kind of local repository (files on disk, source control, etc).  Since Visual Studio is geared towards text file-based storage, I concentrated on the different ways I can get currently executing objects into some text format for editing and storage.

Originally, we had used a complicated system of interfaces and configuration objects that were designed for serialization to XML.  It worked, and allowed us to control how things were serialized and deserialized, but there were some major issues.  Not the least of which was the fact that since we weren't serializing objects but their configurations, our object graph relationships had to be stored as ids and assembly qualified names in generic collections.  THAT meant that we were pushing dependencies on our serialization and deserialization objects deep into these object trees from the root object (which we controlled) to the leaves (which we planned to be add-ins written by people outside of our control).  That made our API much more complicated and gave add-in writers much more control over the system than I wanted.

In addition to the add-in issues, since our references had to be stored as Guid/type generic collections, our configuration objects serialization was a pain in the ass.  You cannot serialize dictionaries into XML.  You have to implement IXmlSerializable and manually control serialization and deserialization.

Breakdown

And this is the point where the entire system breaks down.  Pretty much every object that would be edited within the designer could, hypothetically, be an add-in, written by some doofus.  They would be responsible for implementing our confusing configuration system correctly, which would mean having a configuration object that would be serializable to XML, which is hard enough without the fact that they would be forced to handle serializing dictionaries.  IN ADDITION, it is VERY hard to manually control XML serialization in a generic way–i.e., so that element or property X can be found before or after Y or Z.  I'd wager to say 30% of the coders out there would use StringBuilder and string.Split to handle XML serialization, and another 60% would use the correct XML DOM but would only be able to handle elements in an expected order.  Considering I wanted designers to be able to edit these serialized objects directly via a text editor, this was simply unacceptable.  And so comes XAML serialization.

XAML to the rescue

XAML is designed to serialize standard CLR object graphs.  That means you can feed your average .NET object directly to the serializer with a minimum of muss or fuss.  Visual Studio Core comes with the XAML editor, which includes great intellisense and validation support (as long as you provide an XSD).  In addition, the simplicity of XAML serialization means that you can pretty much leave everything up to the serializer–as long as it passes validation, the XAML file will deserialize back into the original object.  Almost.

The more you know…

There are some things you need to be aware of, but its much less of a hassle than XML serialization.

First off, your objects must have a parameterless constructor.  This is pretty much standard for serialization work.  So that means if you reference an object that needs to be serialized, and it doesn't include a parameterless constructor, and you don't control its code, you'll have to wrap it in a proxy object that does have a parameterless constructor.

Second, you can't serialize public fields via the XAML serializer.  Only public properties will be serialized.  Non-collection properties should have a public getter and a public setter.  Collection properties should have a public getter, and should never return a null.  These properties also need to be marked with a special attribute as well (see below).

Third, serialization can be controlled declaratively via the DesignerSerializationVisibility attribute.  This attribute takes an enum that indicates the property is either Visible, Content or Hidden.  Obviously, if you don't want your property serialized you would mark it as Hidden or make its setter private.  Collection properties (which should not have public setters) should be marked as Content, because public properties without setters are treated as Hidden.  The default for any property without this attribute is Visible.

Fourth, generics are supported, but generic dictionaries still suck.  There are two ways to get around this.  First, you can create a serializer that extends CodeDomSerializer and instruct the XAML serializer to use this for the generic dictionary by marking it with the DesignerSerializer attribute.  Good luck if you want to do this; there isn't any documentation on it that's worth a damn.  An easier way to handle generic dictionaries is to create a wrapper that extends KeyedCollection.  KeyedCollection is a specialized type of dictionary that the XAML serializer can handle easily, yet still provides some of the benefits of generics. 

Examples

Here's a serializable object with a standard property of type string, and a couple collections.  The generic dictionary collection Fail won't deserialize property, whereas the KeyedCollection wrapped by MyGenericDictionary serializes just fine.

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">class</span><span> </span><span style="color: #2b91af">SerializedObjectLol</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">Name</span><span> { </span><span style="font-weight: bold; color: #2f2fff">get</span><span>; </span><span style="font-weight: bold; color: #2f2fff">set</span><span>; }</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">private</span><span> </span><span style="color: #2b91af">Dictionary</span><span style="color: fuchsia">&lt;</span><span style="color: #761dbe">Guid</span><span>, </span><span style="color: #2b91af">Type</span><span style="color: fuchsia">&gt;</span><span> </span><span style="color: navy">_dict</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; [</span><span style="color: #2b91af">DesignerSerializationVisibility</span><span>(</span><span style="color: #a927b4">DesignerSerializationVisibility</span><span style="color: fuchsia">.</span><span style="color: navy">Content</span><span>)]</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="color: #2b91af">Dictionary</span><span style="color: fuchsia">&lt;</span><span style="color: #761dbe">Guid</span><span>, </span><span style="color: #2b91af">Type</span><span style="color: fuchsia">&gt;</span><span> </span><span style="color: navy">Fail</span><span> { </span><span style="font-weight: bold; color: #2f2fff">get</span><span> { </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: navy">_dict</span><span> </span><span style="color: fuchsia">??</span><span> (</span><span style="color: navy">_dict</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">Dictionary</span><span style="color: fuchsia">&lt;</span><span style="color: #761dbe">Guid</span><span>, </span><span style="color: #2b91af">Type</span><span style="color: fuchsia">&gt;</span><span>()); } }</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">private</span><span> </span><span style="color: #2b91af">MyGenericDictionary</span><span> </span><span style="color: navy">_dictionary</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; [</span><span style="color: #2b91af">DesignerSerializationVisibility</span><span>(</span><span style="color: #a927b4">DesignerSerializationVisibility</span><span style="color: fuchsia">.</span><span style="color: navy">Content</span><span>)]</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="color: #2b91af">KeyedCollection</span><span style="color: fuchsia">&lt;</span><span style="color: #761dbe">Guid</span><span>, </span><span style="color: #2b91af">GuidTypePair</span><span style="color: fuchsia">&gt;</span><span> </span><span style="color: navy">MyDictionary</span><span> { </span><span style="font-weight: bold; color: #2f2fff">get</span><span> { </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: navy">_dictionary</span><span> </span><span style="color: fuchsia">??</span><span> (</span><span style="color: navy">_dictionary</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">MyGenericDictionary</span><span>()); } }</span>

<span>}</span>

To wrap a KeyedCollection, you have to do two things.  First, you have to provide a class that wraps your key/value pair, providing public getters and setters for their properties (the generic KeyValuePair is immutable, so you can't use it).  KeyedCollection is abstract, so you'll have to override a single method, GetKeyForItem.  This method asks you to provide the base collection with the key for the type being stored in the collection and is simple to implement. 

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">class</span><span> </span><span style="color: #2b91af">MyGenericDictionary</span><span> : </span><span style="color: #2b91af">KeyedCollection</span><span style="color: fuchsia">&lt;</span><span style="color: #761dbe">Guid</span><span>, </span><span style="color: #2b91af">GuidTypePair</span><span style="color: fuchsia">&gt;</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">protected</span><span> </span><span style="font-weight: bold; color: #2f2fff">override</span><span> </span><span style="color: #761dbe">Guid</span><span> </span><span style="color: navy">GetKeyForItem</span><span>(</span><span style="color: #2b91af">GuidTypePair</span><span> </span><span style="color: navy">item</span><span>)</span>

<span>&nbsp;&nbsp;&nbsp; {</span>

<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: navy">item</span><span style="color: fuchsia">.</span><span style="color: navy">Key</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; }</span>

<span>}</span>

&nbsp;

<span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="font-weight: bold; color: #2f2fff">class</span><span> </span><span style="color: #2b91af">GuidTypePair</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="color: #761dbe">Guid</span><span> </span><span style="color: navy">Key</span><span> { </span><span style="font-weight: bold; color: #2f2fff">get</span><span>; </span><span style="font-weight: bold; color: #2f2fff">set</span><span>; }</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">public</span><span> </span><span style="color: #2b91af">Type</span><span> </span><span style="color: navy">Value</span><span> { </span><span style="font-weight: bold; color: #2f2fff">get</span><span>; </span><span style="font-weight: bold; color: #2f2fff">set</span><span>;&nbsp; }</span>

<span>}</span>

Notice the type arguments provided the base collection.  The first type is the type of the key; the second type is the type of the object that contains both the key and the value, not the type of the value. 

Here's the code for serialization and deserialization (performed in memory):

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;summary&gt;</span>

<span style="color: gray">///</span><span style="color: green"> Serializes the specified object</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;/summary&gt;</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;param name=&quot;toSerialize&quot;&gt;</span><span style="color: green">Object to serialize.</span><span style="color: gray">&lt;/param&gt;</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;returns&gt;</span><span style="color: green">The object serialized to XAML</span><span style="color: gray">&lt;/returns&gt;</span>

<span style="font-weight: bold; color: #2f2fff">private</span><span> </span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">Serialize</span><span>(</span><span style="font-weight: bold; color: #2f2fff">object</span><span> </span><span style="color: navy">toSerialize</span><span>)</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">XmlWriterSettings</span><span> </span><span style="color: navy">settings</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">XmlWriterSettings</span><span>();</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// You might want to wrap these in #if DEBUG&#39;s </span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">settings</span><span style="color: fuchsia">.</span><span style="color: navy">Indent</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">true</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">settings</span><span style="color: fuchsia">.</span><span style="color: navy">NewLineOnAttributes</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">true</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// this gets rid of the XML version </span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">settings</span><span style="color: fuchsia">.</span><span style="color: navy">ConformanceLevel</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: #a927b4">ConformanceLevel</span><span style="color: fuchsia">.</span><span style="color: navy">Fragment</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// buffer to a stringbuilder</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">StringBuilder</span><span> </span><span style="color: navy">sb</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">StringBuilder</span><span>();</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">XmlWriter</span><span> </span><span style="color: navy">writer</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: #2b91af">XmlWriter</span><span style="color: fuchsia">.</span><span style="color: navy">Create</span><span>(</span><span style="color: navy">sb</span><span>, </span><span style="color: navy">settings</span><span>);</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// Need moar documentation on the manager, plox MSDN</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">XamlDesignerSerializationManager</span><span> </span><span style="color: navy">manager</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">XamlDesignerSerializationManager</span><span>(</span><span style="color: navy">writer</span><span>);</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">manager</span><span style="color: fuchsia">.</span><span style="color: navy">XamlWriterMode</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="color: #a927b4">XamlWriterMode</span><span style="color: fuchsia">.</span><span style="color: navy">Expression</span><span>;</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// its extremely rare for this to throw an exception</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">XamlWriter</span><span style="color: fuchsia">.</span><span style="color: navy">Save</span><span>(</span><span style="color: navy">toSerialize</span><span>, </span><span style="color: navy">manager</span><span>);</span>

&nbsp;

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: navy">sb</span><span style="color: fuchsia">.</span><span style="color: navy">ToString</span><span>();</span>

<span>}</span>

&nbsp;

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;summary&gt;</span>

<span style="color: gray">///</span><span style="color: green"> Deserializes an object from xaml.</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;/summary&gt;</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;param name=&quot;xamlText&quot;&gt;</span><span style="color: green">The xaml text.</span><span style="color: gray">&lt;/param&gt;</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;returns&gt;</span><span style="color: green">The deserialized object</span><span style="color: gray">&lt;/returns&gt;</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;exception cref=&quot;XmlException&quot;&gt;</span><span style="color: green">Thrown if the serialized text is not well formed XML</span><span style="color: gray">&lt;/exception&gt;</span>

<span style="color: gray">///</span><span style="color: green"> </span><span style="color: gray">&lt;exception cref=&quot;XamlParseException&quot;&gt;</span><span style="color: green">Thrown if unable to deserialize from xaml</span><span style="color: gray">&lt;/exception&gt;</span>

<span style="font-weight: bold; color: #2f2fff">private</span><span> </span><span style="font-weight: bold; color: #2f2fff">object</span><span> </span><span style="color: navy">Deserialize</span><span>(</span><span style="font-weight: bold; color: #2f2fff">string</span><span> </span><span style="color: navy">xamlText</span><span>)</span>

<span>{</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: #2b91af">XmlDocument</span><span> </span><span style="color: navy">doc</span><span> </span><span style="color: fuchsia">=</span><span> </span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">XmlDocument</span><span>();</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// may throw XmlException</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: navy">doc</span><span style="color: fuchsia">.</span><span style="color: navy">LoadXml</span><span>(</span><span style="color: navy">xamlText</span><span>);</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="color: green">// may throw XamlParseException</span>

<span>&nbsp;&nbsp;&nbsp; </span><span style="font-weight: bold; color: #2f2fff">return</span><span> </span><span style="color: #2b91af">XamlReader</span><span style="color: fuchsia">.</span><span style="color: navy">Load</span><span>(</span><span style="font-weight: bold; color: #2f2fff">new</span><span> </span><span style="color: #2b91af">XmlNodeReader</span><span>(</span><span style="color: navy">doc</span><span>));</span>

<span>}</span>

The serialize method is a bit of overkill; you can typically use the override for XamlWriter.Save that takes an object and returns a string.  The version I provided tabifys the result so its nicer to read.  An interesting side note is the XamlDesignerSerializationManager, which has methods to add and get "service providers" for use, I assume, during serialization.  There is no documentation available about this manager or how these providers are used. I'd guess that if you mark a property with the DesignerSerializerAttribute, this is where you'd find them. 

TL;DR

XAML serialization definitely beats XML for working with object graphs in a text format.  It works on public properties only, and requires you to have a parameterless constructor.  You can control how properties are serialized using the DesignerSerializationVisibility and DesignerSerializer attributes.  And while the XAML serializer doesn't freak out about generics, you still have to abandon your generic dictionaries for collections that extend KeyedCollection. 

UPDATE!

I'd like to say "Hi!" to the guys at Microsoft reading this.  Yes, I know you're reading this; you sent this blog post, copied and pasted into a word document, to me in response to some questions about XAML serialization.  Look, if you want me to do your work, that's great.  My rates are reasonable.  Just ask me next time and I'll even edit out the "sucks" for you!

kick it on DotNetKicks.com

2 Responses to XAML Serialization FTW

  1. Chris Woodward says:

    Thanks for this. It will help me get around the quirk I found, which is that Dictionaries are ok so long as they are not empty. The type info is serialized in the item and Xaml is clever enough to infer it from that if an item is present.

    public class MyClass
    {
    public string S { get; set; }

    private Dictionary<string, string> _dictionary = new Dictionary<string, string>();
    public Dictionary<string, string> Dictionary
    {
    get { return _dictionary; }
    set { _dictionary = value; }
    }
    }

    [TestMethod]
    public void SaveGenericDictionaryTestOK()
    {
    MyClass o = new MyClass();
    o.Dictionary["foo"] = "bar";

    StringBuilder sb = new StringBuilder();
    XmlWriter writer = XmlWriter.Create(sb);
    XamlWriter.Save(o, writer);

    XmlReader reader = XmlReader.Create(new StringReader(sb.ToString()));
    MyClass loaded = XamlReader.Load(reader) as MyClass;

    Assert.AreEqual("bar", loaded.Dictionary["foo"]);
    }

    [TestMethod]
    public void SaveGenericDictionaryTestFail()
    {
    MyClass o = new MyClass();
    o.S = "test";

    StringBuilder sb = new StringBuilder();
    XmlWriter writer = XmlWriter.Create(sb);
    XamlWriter.Save(o, writer);

    XmlReader reader = XmlReader.Create(new StringReader(sb.ToString()));
    MyClass loaded = XamlReader.Load(reader) as MyClass;

    Assert.AreEqual("test", loaded.S);
    }

    The Xaml produced in the passing test is <?xml version="1.0" encoding="utf-16"?><MyClass S="{x:Null}" xmlns="clr-namespace:TestProject.Configuration;assembly=TestProject" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; xmlns:s="clr-namespace:System;assembly=mscorlib"><MyClass.Dictionary><s:String x:Key="foo">bar</s:String></MyClass.Dictionary></MyClass>

  2. Will says:

    Ah, yes, it works for strings.

    And that’s all.

    I got myself in a little trouble because I used a string/string dictionary to test xaml serialization. It was only when I moved to a Guid/Type dictionary did I find out that it doesn’t work so much. Try it yourself!

Comments are closed.