InitializeComponent for multiple assembly versions (WPF)
In the code behind of WPF controls the constructor loads the corresponding XAML1 file (code). The code for loading the XAML is automatically generated2 when calling
InitializeComponent(), which one can find in virtually every control’s constructor. Unfortunately it might not play well, break, when there are multiple versions loaded of the same assembly containing the control (in the same AppDomain). The solution is simple but, alas, not well documented.
WPF is old, first released in 2006, and open source by now. The issue described here has been know for a long time (< 2009) and reported to Microsoft but was labelled as ‘Won’t fix’. Unfortunately the original report is lost and it is hard to trace whether any reason was provided. The documentation is nonexistent, therefore i wanted to write this post to keep the information alive in the internet hive mind. This post is based on the blogpost by Alex Feinberg (2014) which led me to the actual solution.
The problem usually surfaces when the following exception3 pops-up when the control is being loaded.
Type : System.Exception Source : PresentationFramework Target : Application Message below : > The component 'MyNamespace.MyView' does not have a resource identified by the URI '/MyLibrary;component/MyView.xaml'. ---------------------------------------- at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator) at MyNamespace.MyView.InitializeComponent()
Exception indicating the XAML cannot be found
This post is written for
.NET framework 4.8.
Problem at hand
C# supports loading multiple versions of the same assembly in a single AppDomain. When the control is loaded its constructor is called, which loads its XAML (by
InitializeComponent()), stored as a resource in the assembly. Determining which XAML resource in the assembly should be used is determined by specifying its
URI (uniform resource identifier);
article. As one can clearly see this in the generated code for
Generated code as found in
Notice however it contains the assembly (
MyLibrary) and XAML name (
MyView.xaml) but not a version , at least it probably does not given you are reading this post. This is where it goes wrong, in a multi version environment this information is not sufficient to uniquely specify the resource. The URI class supports various types of resources (from FTP, file to embedded resource in the assembly) and optional parameters, one of them being a version.
So what is the problem?
WPF internally caches XAML streams and in a multi version environment, if the URI does not specify a version, the wrong cached XAML resource might be used failing a check with a misleading error message:
The component '...' does not have a resource identified by the URI '...', even though the resource is present.
Working backwards the original exception in thrown in
LoadComponent(...) which actually loads the XAML and is called by
InitializeComponent(). The highlighted assembly check will fail.
Source: WPF sourcecode
If it is loaded for the first time,
else, the code goes to
GetResourceOrContentPart(..) which uses
GetResourcePackage(..) to get the Package (containing the XAML resource). It uses the
PreloadedPackages cache (
PackageStore), based on URI. The URI will not contain the version and thus will collide if there are multiple versions of the same assembly in use.
If this component has been loaded before the
s_NestedBamlLoadInfo) stack is kept in a local variable so that the Outer LoadBaml and Inner LoadBaml( ) for the same Uri share the related information.
To determine why
InitializeComponent() does not include the version in the URI one has to look in
GenerateInitializeComponent(), which will be called during compilation. There is explicit code to handle and parse the version, see highlighted.
Source: WPF sourcecode
It is well documented, i’m still using .NET Framework style projects and as per code documentation: “During MarkupCompilation, the AssemblyVersion property would not be set and WPF would correctly generate a resource URI without a version.”, this is the issue. Idiomatic the
AssemblyVersion property is not set (
AssemblyInfo.cs is used instead which is not sufficient in this case) so WPF generates the URI without version included leading to the problems discussed earlier.
Unfortunately this is nowhere documented besides the source code (fortunately it is open source).
AssemblyVersion property in your
.csproj file. When set
InitializeComponent() will generated code that specifies the version in the URI as well, resolving the problem.
If you prefer to only specify it in
AssemblyInfo.cs though, one can use a msbuild
Target to add the
AssemblyVersion property to the project before being compiled. Reducing maintenance. For example:
<?xml version="1.0" encoding="utf-8"?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Target Name="BeforeBuild"> <ReadLinesFromFile File="$(MSBuildProjectDirectory)\Properties\AssemblyInfo.cs"> <Output TaskParameter="Lines" ItemName="ItemsFromFile" /> </ReadLinesFromFile> <PropertyGroup> <Pattern>\[assembly: AssemblyVersion\(.(\d+)\.(\d+)\.(\d+)\.(\d+)</Pattern> <In>@(ItemsFromFile)</In> <Out>$([System.Text.RegularExpressions.Regex]::Match($(In), $(Pattern)))</Out> </PropertyGroup> <CreateProperty Value="$(Out.Remove(0, 28))"> <Output TaskParameter="Value" PropertyName="AssemblyVersion" /> </CreateProperty> </Target> </Project
Alternative one could manually patch the
*.g.cs files and add the version number, although i would strongly recommend the former solution.
One could also more to the SDK-style projects for which it is idiomatic to work with the
As last alternative one could ditch the default
InitializeComponent() and create your
own. (and if you want to be fancy use a extension method and source generator, C# 9 or above is required)
technically BAML (Binary Application Markup Language) will be loaded instead of XAML. It is a pre-tokenized binary version of the XAML code for improved performance. ↩︎
the generated code can be found in
MyView.g.cslocated in the
- Permalink: //oostens.me/posts/initializecomponent-for-multiple-assembly-versions-wpf/
- License: The text and content is licensed under CC BY-NC-SA 4.0. All source code I wrote on this page is licensed under The Unlicense; do as you please, I'm not liable nor provide warranty.