Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An XAML Serializer Preserving Bindings

0.00/5 (No votes)
1 Nov 2007 1  
Saving XAML files using MarkupWriter

Introduction

Unfortunately XamlWriter is not capable of preserving bindings (see Serialization Limitations of XamlWriter.Save for more details). I have Googled for a couple of days now but I did not find any solution yet (if somebody already has one, please let me know).
Therefore I created the XamlSerializer class which can save Bindings.

Background

While Googling, I figured out that it is possible to get some information about how to serialize an object by using MarkupWriter.GetMarkupObjectFor; it returns a MarkupObject for the provided object. For all properties of the object which should be serialized, the MarkupObject provides a MarkupProperty in its MarkupObject.Properties collection.
Using that MarkupObject of the root object, it is possible to dig through the object's properties and contained objects.

How Does It Work?

Equipped with that information, I have started implementing XamlSerializer which is capable of writing XAML and storing property bindings. Since XamlReader has no difficulties in understanding bindings, XamlSerializer is loading XAML by using XamlReader.Load.

In XAML, each object is represented by a tag with the name of the object's type; properties are either stored as attributes or also as tags (composite properties). For content properties (specified by the ContentProperty attribute on the containing class) only their values are stored. Therefore XamlSerializer creates XAML using XmlDocument, because then it is not necessary to sort properties before storing them (attributes first, then elements for composite properties and finally the value of the content property), instead they can be stored in the order MarkupObject.Properties which returns by just adding an attribute or appending a child element.

Saving an object is done with the SaveObject method which expects the parent XmlElement and an appropriate MarkupObject.

private void SaveObject(XmlElement parent, MarkupObject markupObject)

SaveObject calls SaveProperty for all properties returned by MarkupObject.Properties passing the XmlElement of the object, the MarkupObject and the corresponding MarkupProperty.

private void SaveProperty
    (XmlElement parent, MarkupObject markupObject, MarkupProperty property)

SaveProperty first checks if it is a dependency property (MarkupProperty.DependencyProperty). If it is one, BindingOperations.GetBinding is called to get the Binding for that property. If a Binding exists the property is stored as an element, the retrieved Binding is stored using the MarkupObject returned by MarkupWriter.GetMarkupObjectFor called with that Binding. Bound properties are stored in SaveBindingProperty.

private void SaveBindingProperty
    (XmlElement parent, MarkupProperty property, Binding binding)

If the property is not a dependency property, it is checked if it is the content property of the object then only its content is stored using SavePropertyContent.

private void SavePropertyContent(XmlElement parent, MarkupProperty property)

Composite properties are stored using an XmlElement within SaveCompositeProperty which calls SavePropertyContent to store the property's value.

private void SaveCompositeProperty(XmlElement parent, MarkupProperty property)

And finally properties which can be stored as strings in attributes are saved by SaveAttributeProperty.

private void SaveAttributeProperty(XmlElement parent, MarkupProperty property)

Namespaces

For all objects and properties which are not contained in the namespace if the root object XAML expects appropriate prefixes and URI definitions. These are generated by GetNamespacePrefix and GetNamespaceUri. Both methods are expecting a Type to retrieve the namespace and the assembly.

private string GetNamespacePrefix(Type type)
private string GetNamespaceUri(Type type)

For each namespace/assembly combination a prefix and a URI definition of the form xmlns:PREFIX="clr-namespace:NAMESPACE;assembly=ASSEMBLY" is added to the DocumentElement of the XmlDocument. For namespaces which are from the assembly PresentationFramework, the URI http://schemas.microsoft.com/winfx/2006/xaml/presentation is used. Prefixes are stored in a Dictionary to avoid multiple definitions.

Using the Code

I have created the project with Microsoft Visual C# 2008 Express Edition (.NET 3.5); I have no idea if it is running with .NET 3.0.

Using XamlSerializer is really easy: It provides some static methods to load and save XAML. To load an object stored in XAML, you can use Load while saving is done with Save (I guess this is obvious).

//==========================================================================
/// <summary>
///   Saves the provided object as XAML into the specified 
/// <see cref="XmlWriter"/>.
/// </summary>
/// <param name="instance">
///   The <see cref="Object"/> to save.
/// </param>
/// <param name="writer">
///   The <see cref="XmlWriter"/> to use for writing the XAML.
/// </param>
public static void Save(object instance, XmlWriter writer);

//==========================================================================
/// <summary>
///   Saves the provided object as XAML into the specified 
/// <see cref="TextWriter"/>.
/// </summary>
/// <param name="instance">
///   The <see cref="Object"/> to save.
/// </param>
/// <param name="writer">
///   The <see cref="TextWriter"/> to use for writing the XAML.
/// </param>
public static void Save(object instance, TextWriter writer);

//==========================================================================
/// <summary>
///   Saves the provided object as XAML into the specified 
///   <see cref="Stream"/>.
/// </summary>
/// <param name="instance">
///   The <see cref="Object"/> to save.
/// </param>
/// <param name="stream">
///   The <see cref="Stream"/> to use for writing the XAML.
/// </param>
public static void Save(object instance, Stream stream);

//==========================================================================
/// <summary>
///   Saves the provided object as XAML into the specified 
/// <see cref="string"/>.
/// </summary>
/// <param name="instance">
///   The <see cref="Object"/> to save.
/// </param>
/// <returns>
///   A <see cref="string"/> containing the XAML.
/// </returns>
public static string Save(object instance);

//==========================================================================
/// <summary>
///   Reads the XAML in the specified <see cref="Stream"/> and returns 
///   the root object.
/// </summary>
/// <param name="stream">
///   The <see cref="Stream"/> providing the XAML.
/// </param>
/// <returns>
///   The root object of the read XAML.
/// </returns>
public static object Load(Stream stream);

//==========================================================================
/// <summary>
///   Reads the XAML in the specified <see cref="XmlReader"/> and returns 
///   the root object.
/// </summary>
/// <param name="reader">
///   The <see cref="XmlReader"/> providing the XAML.
/// </param>
/// <returns>
///   The root object of the read XAML.
/// </returns>
public static object Load(XmlReader reader);

//==========================================================================
/// <summary>
///   Reads the XAML in the specified <see cref="string"/> and returns 
///   the root object.
/// </summary>
/// <param name="value">
///   The <see cref="string"/> providing the XAML.
/// </param>
/// <returns>
///   The root object of the read XAML.
/// </returns>
public static object Load(string value); 

Summary

Although there are still a lot of issues and bugs, XamlSerializer now makes it possible to preserve bindings when serializing objects to XAML. I have only done some basic tests with easy XAML files and I am sure there are many scenarios XamlSerializer cannot handle yet; but it will hopefully be a good starting point.

History

  • 2007-11-01: Article submitted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here