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 string
s 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 namespace
s 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).
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);
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