TL;DR: This article talks about the internals of the WinRT/Xaml implementation in Windows 8 and how it deals with databinding, its use of the IXamlMetadataProvider interface, tips & tricks around it, and how to extend the resolver to create dynamic databinding scenarios.
Xaml has been around for a while, and it’s been a big part of Silverlight and WPF. Both frameworks are mostly managed, and use a CLR feature known as Reflection, or type introspection.
This is a very handy feature used by Silverlight/WPF to enable late binding to data types, where strings can be used to find their actual classes counter-parts, either for value converters, UserControls, Data-Binding, etc...
The burden of .NET reflection
It comes with a cost, though. Reflection is a very expensive process, and up until very recently in Silverlight, there was no way to avoid the use of Reflection. The recent addition of the ICustomTypeProvider interface allows for late binding without the use of reflection, which is a big step what I think is the right direction. Having this kind of interface allows for custom types that define pre-computed lists of fields and properties, without having the runtime to load every metadata available for an object, and perform expensive type safety checks.
This burden of the reflection is particularly visible on Windows Phone, where it is suggested to limit the use of DataBinding, which is performed on the UI thread. The Silverlight runtime needs to walk the types metadata to find observable properties so that it can properly perfrom one or two-way bindings, and this is very expensive.
There are ways to work around this without having ICustomTypeProvider, mainly by generating code that does everything the Xaml parser and DataBinding engines do, but it’s mainly experimental, yet it gives great results.
WinRT, native code and the lack of Reflection
In Windows 8, WinRT is pure native code, and now integrates what used to be the WPF/Xaml engine. This new engine can be seen at the cross roads of Silverlight, WPF and Silverlight for Windows Phone. This new iteration takes a bit of every framework, with some tweaks.
These tweaks are mainly related to the fact that WinRT is a native COM based API, that can be used indifferently from C# or C++.
For instance, xml namespaces have changed form and cannot reference assemblies anymore. Declarations that used to look like this :
Now look like this :
Where the using only defines the namespace to be used to find the types specified in the inner xaml.
Additionally, WinRT does not know anything about .NET and the CLR, meaning it cannot do reflection. This means that the Xaml implentation in WinRT, to be compatible with the way we all know Xaml, needs to be able to do some kind of reflection.
Meet the IXamlMetadataProvider interface
To be able to do some kind reflection, the new Metro Style Applications profile generates code based on the types that are used in the Xaml files of the project. It takes the form of a hidden file, named XamlTypeInfo.g.cs.
That file can be found in the “obj” folder under any Metro Style project that contains a Xaml file. To find it, just click on the “Show all files” button at the top of the Solution Explorer file. You may need to compile the project for it to be generated.
In the entry assembly, the file contains a partial class that extends the App class to make it implement the IXamlMetadataProvider interface. WinRT uses this interface to query for the details of types it found while parsing Xaml files.
This type acts as map for every type used in all Xaml files a project, so that WinRT can get a definition it can understand, in the form of IXamlType and IXamlMember instances. This takes the form of a big switch/case construct, that contains string representation of fully qualified types. See this example :
private IXamlType CreateXamlType(string typeName)
XamlSystemBaseType xamlType = null;
xamlType = new XamlSystemBaseType(typeName, typeof(Windows.UI.Xaml.Controls.UserControl));
userType = new XamlUserType(this, typeName, typeof(Application1.Common.RichTextColumns), GetXamlTypeByName("Windows.UI.Xaml.Controls.Panel"));
userType.Activator = Activate_3_RichTextColumns;
xamlType = userType;
It also creates hardcoded methods that can explicitly get or set the value of every properties a DependencyObject, like this :
userType = (XamlUserType)GetXamlTypeByName("Application1.Common.RichTextColumns");
xamlMember = new XamlMember(this, "RichTextContent", "Windows.UI.Xaml.Controls.RichTextBlock");
xamlMember.Getter = get_1_RichTextColumns_RichTextContent;
xamlMember.Setter = set_1_RichTextColumns_RichTextContent;
Note that if you want to step into this code without the debugger ignoring you, you need to disable the “Just my code” feature in the debugger options.
Also, in case you wonder, the Code Generator scans for all referenced assemblies for implementations of the IXamlMetadataProvider interface, and will generate code that will query these providers to find Xaml type definitions.
Code Generation is good for you
Now, this code generation approach is very interesting for some reasons.
The first and foremost is performance, because the runtime does not need to use reflection to determine what can be computed at runtime. This is a enormous performance gain, and this will be beneficial for the perceived performance as the runtime will not waste precious CPU cycles to compute data that can be determined at compile time.
More generally, in my projects, I've been using this approach of generating as much code as possible, to avoid using reflection and waste time and battery on something that can be only done once and for all.
The second reason is extensibility, as this IXamlMetadataProvider can be extended to add user-provided types that are not based on DependencyObject. This is an other good impact on performance.
Adding custom IXamlMetadataProvider
It is possible to extend the lookup behavior for standard types that are not dependency objects. This opens the same range of scenarios that ICustomTypeProvider provides.
All that is needed is to implement the IXamlMetadataProvider interface somewhere in an assembly, and the code generator used for XamlTypeInfo.g.cs will pick those up and add them in the Xaml type resolution chain. Note that for some unknown reason, it does not work in the main assembly but only for referenced assemblies.
Every time the databinding engine will need to get the value behind a databinding expression, it will call the IXamlMetadataProvider.GetXamlType method to get the definition of that type, then get the databound property value.
A very good feature, if you ask me.
The case of hidden DependencyObject
By hidden dependency properties, I’m talking about DependencyObject types that are not directly referenced in Xaml files. This can be pretty useful for complex controls that generate convention based databinding, such as the SemanticZoom control, that provides a implicit “Key” property to perform the Zoomed out view.
Since this XamlTypeInfo.g.cs code is generated from all known Xaml files, this means that these hidden DependencyObject types that do not have code generated for them. This forces the CLR to intercept these failed requests and fallback on actual .NET reflection based property searching for databinding, which is not good for performance.
This fallback behavior was not implemented in the Developer Preview, and the binding would just fail with a NullReferenceException without any specific reason given to the developer.
The case of Xaml files located in another assembly
If your architecting a bit your solution, you’re probably using MVVM or a similar pattern, and you’re probably putting your views in another assembly.
If you do that, this means that there will not be any xaml file in your main assembly (aside from the App.xaml file), leading to an empty XamlTypeInfo.g.cs file. This will make any type resolution requested by WinRT fail, and your application will mostly likely not run.
In this case, all you need to do is create a dummy Xaml file that will force the generation of the XamlTypeInfo.g.cs, and basically make your layer separation work.
Until next time, happy WinRT'ing !