Solving cross-assembly WPF Resource problems once and for all

I wrote several posts about WPF weakness when it comes to sharing resources. Our use case, at my at work, where we load multiple versions of an assembly into a single app domain this problem has been felt most acutely. As I wrote previously, using the most documented way of loading resource dictionaries, unless you specify a version of an assembly when loading a resource dictionary you are running the risk of loading a wrong resource dictionary because you may have multiple versions of same library loaded into an app domain. And such problems are notoriously difficult to catch and understand.

At least now, it’s easy to solve. I am glad to announce that I found a definitive way to share resources across assembly boundaries without running into versioning issues. The key to doing this is to never load resource dictionary from another assembly using cross-assembly Uris. I did not know that there was another way until recently. All the examples of loading resource dictionaries, that I can recall, use the Source=”{PackUri}” method of loading, but there’s a way to reference resource dictionary using code.

A ResourceDictionary, just like any other class that can be defined in Xaml, can also contain the “code behind” portion, a source code file associated with the main Xaml file. If you already have resources defined in a resource dictionary somewhere in your assembly, you don’t have to change it. Instead you’ll create a brand new resource dictionary which you would use to export the resource for sharing. For example, let’s say you have a resource dictionary defining the colors of the stoplight. It lives in an assembly called ResourceSharing, in a namespace ResourceSharing.Resources in a file called Resources.xaml.


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <SolidColorBrush x:Key="Go" Color="Green"/>
    <SolidColorBrush x:Key="Stop" Color="Red"/>
    <SolidColorBrush x:Key="PrepareToStop" Color="Yellow"/>
    
</ResourceDictionary>

To export this ResourceDictionary, create a new Resource Dictionary for export. Let’s call it ResourceSharing.Resources.ExportResourceDictionary. I’ll consist of the xaml and the C# portions

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                    x:Class="ResourceSharing.Resources.ExportResourceDictionary"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="Resources.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    
</ResourceDictionary>

Notice the Class definition on line 2.

The code behind class looks like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ResourceSharing.Resources
{
    public partial class ExportResourceDictionary
    {
        //Expose it as singleton to avoid multiple instances of this dictionary
        private static readonly ExportResourceDictionary _instance = new ExportResourceDictionary();

        public static ExportResourceDictionary Instance
        {
            get { return _instance; }
        }

        public ExportResourceDictionary()
        {
            InitializeComponent();
        }
    }
}

Let’s also create a namespace we can use to make it easier to reference these resources from another assembly using namespaces

using System.Windows.Markup;

[assembly:XmlnsDefinitionAttribute("http://my.schemas.com/web/resources", "ResourceSharing.Resources")]

How to Share

Here’s how to consume the resources exported from the ResourceSharingAssembly. For this example, I had an executable assembly called ResourceSharingClient, where I am referencing exported resource right in the Main Window. Notice the use of the exported namespaces in line 2 and the way that I am referencing a static instance of the ExportResourceDictionary. Such references are version-proof because .NET knows the version of the dependent assemblies. There’s no need to dynamically discover the dependent assembly and the code actually looks a lot neater too. I know that such approach requires a bit more work, but the final result is a lot safer. I only wished that such approach had been advertised by Microsoft WPF evangelists from the beginning.

<Window
        x:Class="ResourceSharingClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:rs="http://my.schemas.com/web/resources"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
        mc:Ignorable="d"
        Title="MainWindow" d:DesignWidth="111.5" d:DesignHeight="109.5" Width="120" Height="130">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <x:Static Member="rs:ExportResourceDictionary.Instance"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Border BorderBrush="Black" BorderThickness="2" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="2" CornerRadius="2" Background="Silver">
        <StackPanel Orientation="Vertical">
            <Ellipse Width="20" Height="20" Fill="{DynamicResource Stop}" Stroke="Black" StrokeThickness="1"/>
            <Ellipse Width="20" Height="20" Fill="{DynamicResource PrepareToStop}" Stroke="Black" StrokeThickness="1"/>
            <Ellipse Width="20" Height="20" Fill="{DynamicResource Go}"  Stroke="Black" StrokeThickness="1"/>
        </StackPanel>
    </Border>
</Window>
Advertisements

6 thoughts on “Solving cross-assembly WPF Resource problems once and for all

    1. Marco

      Hello! Excellent! I was looking for a solution for this for quite some time now. Works cascading, too:) Brilliant!!!

      Reply
  1. Christoph Grün

    Hello! I am using your excellent approach and it works like a charm. I use it for skinning and I have added a method to load an external dictionary on the fly, which works very well.

    Do you have any input on how to update dynamically at runtime if the dictionary was changed? Is this possible with this approach.

    I know the Article is from 2015 but I thought i might give it a shot 🙂

    Reply
    1. alexfeinberg Post author

      You would not change this dictionary, but rather have 2 or more of these and toggle between them by removing one and adding the other at runtime in code. As long as they both have same resource ids and your client code references those resources using dynamicresource you should be good.

      Reply
  2. Christoph Grün

    Thank you very much for your reply – I appreciate you taking the time to answer, but I figured it out myself. I actually now put other (not skin-dependent) global Resources directly in the Exported xaml and read the skin dynamically from a XamlXmlReader with default skins stored as Resources in my low-level Resource assembly. Then i use Shared (static, sorry :)) Functions to swap out skins at run-time even from files from the user’s file-system. It’s not much code, works like a charm and actually integrates – as you stated – so neatly and clean in existing XAML… this absolutely made my day. So to be frank – just know that your snippet is VERY appreciated 🙂

    Reply
  3. Christoph Grün

    And this, to be frank, needs to come off my chest:
    “I only wished that such approach had been advertised by Microsoft WPF evangelists from the beginning.”
    Exactly! Your example is leveraging the true, let’s say “end-customer-compatible” power of WPF in a smart and sensible manner. Showing changes, results, even “wow-effects” on the fly could really have helped to push and sell this amazing platform to developers. I am so in love with WPF and it’s a shame it has never gained much traction, but making things like this “feel” too complicated really was not helping. I am in the process of creating my own toolset – an intermediate framework on top of WPF and .NET, so to speak – and the stable (and beautiful!) things I can mock-up and prototype within minutes is absolutely insane.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s