How to force WPF to use resource URIs that use assembly strong name? Argh!
Asked Answered
F

7

19

O.k, this is really irritating, I had noticed previously that the code generated by WPF for loading XAML resources did not appear to use strong names and therefore may be problematic for scenarios where you need to support side by side versions of WPF assemblies.

This has turned out to be the case, and it's now causing me problems - I have a plug-in system which is supposed to support side by side installation of plugins which differ only in their version numbers (their assembly versions). This of course can be supported by .NET since assemblies are determined to have different identities even if they have the same DLL filename, provided that they are strongly named and either have a different public/private key OR have a different assembly version number.

Now, if we look at the code generated for windows and usercontrols by visual studio, we see in the auto-generated file the following:

/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent() {
    if (_contentLoaded) {
        return;
    }
    _contentLoaded = true;
    System.Uri resourceLocater = new System.Uri("/Sensormatic.AMK1000.Panel;component/views/servicepanelui.xaml", System.UriKind.Relative);

    #line 1 "..\..\..\Views\ServicePanelUI.xaml"
    System.Windows.Application.LoadComponent(this, resourceLocater);

    #line default
    #line hidden
}

Notice the line where the resource locater is created - it is using a relative URI which does not specify the strong name or the version of the assembly which contains the xaml resource.

I thought maybe LoadComponent would check the calling assembly's identity and use it's public key and version details or perhaps check the identity of the assembly which contains the type for the 'this' parameter.

It appears this is not the case - if you have two assemblies with different version numbers (but the same filename) then you can get an IOException with the message "Cannot locate resource X" (for above example "Cannot locate resource 'views/servicepanelui.xaml'.

Worse, I'm pretty sure that this is also going to mean that assemblies with the same filename but different public/private key, i.e. from different publishers, will also result in this error.

So, does anyone know how to get around this? How to make WPF strong name compliant.

Note, as far as I'm concerned this is a WPF bug. You shouldn't have to use Appdomain isolation just to avoid this.

Freiman answered 21/9, 2009 at 6:34 Comment(3)
Hi Phil, I am facing the same issue. were you able to find any solution to this?Stumble
Has anyone faced similar problem with WPF Custom controls using Themes(through Resource Dictionaries)? If Yes, any solution for that?Goines
Is there any progress on this? Surely people are able to create WPF libraries without this problem. I need to but have this exact issue.Marylouisemaryly
P
6

You can set the following in your project file to change the URI's in the generated code:

<PropertyGroup>
  <AssemblyVersion>1.0.0.0</AssemblyVersion>
  <AssemblyPublicKeyToken>[YOUR_PUBLIC_KEY_TOKEN]</AssemblyPublicKeyToken>
</PropertyGroup>
Pegmatite answered 17/5, 2012 at 4:48 Comment(4)
How did you know that? This is so.. well esoteric. this is not, even, officially supported, but it works!Livi
i don't like this solution, what if i change version (no automatic refactor) or i have a different key in my debug machine and the build server ?Dispensatory
I was labouring around this issue now for more than a week and I was suffering very hard! Your answer worked for me like a charm and helped so much! Thank you!! (also note this site, which cited this thread)Wetzel
I was having this problem, which manifested as Could not load file or assembly '<AssemblyName>, Version=1.0.0.0, Culture=neutral' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040). It turns out that I was overwriting the AssemblyVersion property.Riojas
W
5

I have experienced this same problem and this might be a possible solution

each time a control is created using a .xaml page, on the attached .cs file constructor, before the InitializeComponent() call, add the following lines:

contentLoaded = true;
var assemblyName = GetType().Assembly.GetName();
System.Windows.Application.LoadComponent(GetType(), new Uri(
                string.Format("/{0};v{1};component{2}/{3}.xaml",
                assemblyName.Name,
                assemblyName.Version,
                [[[namespace]]],
                type.Name
                ), UriKind.Relative))

where as [[[namespace]]] enter the full namespace of the class, except the visual studio project default namespace

(Note: there is an open a ticked on connect https://connect.microsoft.com/VisualStudio/feedback/details/668914/xaml-generated-code-uses-resource-uri-without-assembly-strong-name)

Winkler answered 14/6, 2011 at 9:39 Comment(1)
in the [[[namespace]]] it's not the namespace but the location relative to the project of the page you're trying to load (that usually coincide to the namespace stripped of the root)Dispensatory
W
3

I tend to agree that this is probably a bug, or at least a deficiency in the XAML tooling. Perhaps you should report it on Connect.

I haven't tried, but here are a couple of potential workarounds:

  1. Inject a pre-build step to automatically modify the .g.cs files to use pack URIs that specify the full assembly information (AssemblyShortName[;Version][;PublicKey];component/Path)
  2. Attach to AppDomain.AssemblyResolve to help the CLR find the right assembly
Whinstone answered 21/9, 2009 at 8:22 Comment(2)
Thanks Kent, though I was hoping there'd be some inbuilt way of handling this! (though I've not found it). I don't think #2 is going to help - pretty sure the assembly resolution request is only going to have the weak name since that's what the URI is specifying, and then I don't know which is the assembly which should be used. #1 might work, though obviously kind of ugly. BTW, great work on truss. Using it in my current project.Freiman
attaching to assemblyresolve, not having a clue of which one you want to load doesn't seems useful to me, maybe you can use the requesting assembly if it's specifiedDispensatory
G
1

I have been grappling with this in VS2012. I couldn't get Riccardo's solution to work in this environment. This variant of his code ...

_contentLoaded = true;
var assemblyName = GetType().Assembly.GetName();
Application.LoadComponent(this, new Uri(String.Format("/{0};v{1};component/CustomersFrame.xaml", assemblyName.Name, assemblyName.Version), UriKind.Relative));

... did resolve the 'cannot locate resource' issue but then I hit the following error a bit further along in a child element: 'Could not register named object. Cannot register duplicate name 'search' in this scope.'

Aaron Marten's solution does work for me. Sorry I can't comment or upvote but I haven't got the rep.

Gallardo answered 2/4, 2013 at 12:38 Comment(0)
L
1

You can also pass the /p:AssemblyVersion=$version parameter to the msbuild process if your builds are automated.

Livi answered 1/11, 2014 at 13:23 Comment(1)
I was labouring around this issue now for more than a week and I was suffering very hard! Your answer worked for me like a charm and helped so much! Thank you!! (also note this site, which cited your answer)Wetzel
D
1

This code, based on Riccardo's answer, worked for me in VS2010.

First I defined a loader method that I can call from my XAML constructor.

namespace Utility
{
    public class Utility
    {
        public static void LoadXaml(Object obj)
        {
            var type = obj.GetType();
            var assemblyName = type.Assembly.GetName();
            var uristring = string.Format("/{0};v{1};component/{2}.xaml",
                assemblyName.Name,
                assemblyName.Version,
                type.Name);
            var uri = new Uri(uristring, UriKind.Relative);
            System.Windows.Application.LoadComponent(obj, uri);
        }
    }
}

Then in the constructor for each XAML control, I replaced InitializeComponent() with:

        _contentLoaded = true;
        Utility.Utility.LoadXaml(this);
        InitializeComponent();

I did notice that some of my RelativeSource bindings stopped working, but I was able to work around this.

Devonne answered 16/11, 2016 at 18:35 Comment(1)
By doing this I get this exception: Cannot locate resource 'basicwizard.xaml'.' where basicwizard is my Window classSalzman
C
1

We also had the same problem, but we had to set the assembly version for some specific projects in our solution only.

Because I liked the idea of setting the version number for the build like user195275 recommended, I did some research of how do do it for a single csproj file.

So in combination with the following thread How to read the assemblyversion from assemblyInfo.cs? We came up with the following solution:

<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>

what it does: It parses the version number out of the AssemblyInfo.cs and set it as Property as in Aaron Martens answer. Which leads to a single point of maintenance for the version number for us.

Concourse answered 16/10, 2018 at 12:53 Comment(1)
Thank you for your answer, after 3 days of intense googling about a similar issue we had (COM-based plugins, which loads different versions of the same WPF assembly), I finally have a working solution! This code also prevent us to edit the csproj file each time we increment our assembly version!Valente

© 2022 - 2024 — McMap. All rights reserved.