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.
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.
[__strong__]private void SaveObject(XmlElement parent, MarkupObject markupObject)
SaveProperty for all properties returned by MarkupObject.Properties passing the XmlElement of the object, the MarkupObject and the corresponding MarkupProperty.
[__strong__]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
[__strong__]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
[__strong__]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.
[__strong__]private void SaveCompositeProperty(XmlElement parent, MarkupProperty property)
And finally properties which can be stored as
strings in attributes are saved by
[__strong__]private void SaveAttributeProperty(XmlElement parent, MarkupProperty property)
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
GetNamespaceUri. Both methods are expecting a Type to retrieve the
namespace and the assembly.
[__strong__]private string GetNamespacePrefix(Type type)
private string GetNamespaceUri(Type type)
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.
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).
public static void Save(object instance, XmlWriter writer);
public static void Save(object instance, TextWriter writer);
public static void Save(object instance, Stream stream);
public static string Save(object instance);
public static object Load(Stream stream);
public static object Load(XmlReader reader);
public static object Load(string value);
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.
- 2007-11-01: Article submitted