Generically sharing Xaml resources
Xaml was introduced in .NET 3.0 and extensively used by WPF. A commonly used construct in WPF is the ResourceDictionary, which defines a collection of resources that can be referenced. Often, ResourceDictionaries are shared by including them in other ResourceDictionaries. When defined in xaml, we might have something like this:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Xaml is not really helping us with the reference to the shared resources in this example. Rather, ResourceDictionary has the logic to initialize itself from a Uri pointing to another ResourceDictionary. ResourceDictionary is certainly not the only data type you can imagine wanting to share and reference from multiple xaml documents. This is especially true with the .NET 4.0 release, where xaml has been completely decoupled from WPF and is much more suitable as a general purpose serialization/deserialization technology.
One simple approach to sharing and referencing xaml documents from other xaml documents is to implement a custom MarkupExtension. With a custom MarkupExtension, our xaml might look something like this:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<Xaml Source="MyResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
This looks almost exactly the same as the first ResourceDictionary example. The major difference is that it relies on a general Xaml MarkupExtension that will load any xaml document. This will work with ResourceDictionaries or any other data type in any xaml context (including WPF). Through the IServiceProvider exposed to MarkupExtensions, the necessary context is available to resolve relative Uris to absolute Uris, which are required for finding and deserializing the referenced xaml documents. A basic version of the Xaml MarkupExtension would look like this:
public class XamlExtension : MarkupExtension
{
public Uri Source { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (this.Source == null)
{
throw new ArgumentNullException("Source");
}
var absoluteUri = this.Source;
if (!absoluteUri.IsAbsoluteUri)
{
var uriContext = (IUriContext)serviceProvider.GetService(typeof(IUriContext));
if (uriContext != null && uriContext.BaseUri != null)
{
absoluteUri = new Uri(uriContext.BaseUri, absoluteUri);
}
}
return XamlServices.Load(absoluteUri.AbsoluteUri);
}
}
The first thing to note about this is the method of resolving relative Uris. The IServiceProvider is queried for an IUriContext, which in turn provides the base Uri. The base Uri will be set appropriately as long as the original xaml document was loaded in such a way that the xaml deserializer can know where it came from. For example, if the xaml document was loaded from a physical file on disk, the xaml deserializer will know the base Uri. On the other hand, if the xaml document was loaded from a stream, the xaml deserializer will have no way of knowing a base Uri for the document being loaded. The second thing to note is that the referenced xaml document is loaded using XamlServices.Load and passing in the absolute Uri as a string. Although the documentation is a bit unclear on XamlServices.Load, the overload that takes a string can in fact be a Uri string. The third thing to note is the method of loading referenced xaml documents. Since an absolute Uri is used, and since the IUriContext is correctly configured when loading from a Uri using XamlServices.Load, multiple layers of references are possible, and references are always relative to the xaml document that contains the reference. In other words, it is valid to have a xaml document that references a xaml document that references a xaml document, etc, even when the xaml documents are located at different Uris (such as in different file system directories).
The last thing to note about this MarkupExtension is its use in the context of pack Uris, which are used extensively in WPF applications. Specifically, the "pack" prefix and web request factory are registered relatively early in the startup of a WPF application, but may not have been registered at the point where the MarkupExtension is invoked during deserialization. When this is the case, the "pack" prefix can be registered manually:
System.Net.WebRequest.RegisterPrefix("pack", new System.IO.Packaging.PackWebRequestFactory());
As always, feedback is welcome.