如何强制 WPF 使用使用程序集强名称的资源 URI?啊!

发布于 2024-08-05 05:30:43 字数 1315 浏览 9 评论 0原文

好吧,这真的很烦人,我之前注意到 WPF 生成的用于加载 XAML 资源的代码似乎没有使用强名称,因此对于需要支持 WPF 程序集并行版本的场景可能会出现问题。

事实证明是这样,现在它给我带来了问题 - 我有一个插件系统,它应该支持并行安装插件,这些插件仅在版本号(它们的程序集版本)上有所不同。 .NET 当然可以支持这一点,因为即使程序集具有相同的 DLL 文件名,但它们也被确定为具有不同的身份,前提是它们是强命名的并且具有不同的公钥/私钥或具有不同的程序集版本号。

现在,如果我们查看 Visual Studio 为 Windows 和用户控件生成的代码,我们会在自动生成的文件中看到以下内容:

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

注意创建资源定位器的行 - 它使用相对 URI,该 URI 未指定强包含 xaml 资源的程序集的名称或版本。

我想也许 LoadComponent 会检查调用程序集的身份并使用它的公钥和版本详细信息,或者可能检查包含“this”参数类型的程序集的身份。

情况似乎并非如此 - 如果您有两个具有不同版本号(但文件名相同)的程序集,那么您可能会收到 IOException 并显示消息“无法定位资源 X”(对于上面的示例“无法定位资源 'views/servicepanelui”) 更糟糕

是,我很确定这也意味着具有相同文件名但不同公钥/私钥(即来自不同发布者)的程序集也会导致此错误。

的 知道如何解决这个问题吗?如何使 WPF 强名称兼容。

注意,据我所知,这是一个 WPF 错误,您不必为了避免这种情况而使用 Appdomain 隔离。 .

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

当爱已成负担 2024-08-12 05:30:43

您可以在项目文件中设置以下内容来更改生成的代码中的 URI:

<PropertyGroup>
  <AssemblyVersion>1.0.0.0</AssemblyVersion>
  <AssemblyPublicKeyToken>[YOUR_PUBLIC_KEY_TOKEN]</AssemblyPublicKeyToken>
</PropertyGroup>

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>
药祭#氼 2024-08-12 05:30:43

我也遇到过同样的问题,这可能是

每次使用 .xaml 页面创建控件时,在附加的 .cs 文件构造函数上,在 InitializeComponent() 调用之前添加以下行:

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

其中 [[[命名空间]]] 输入类的完整命名空间,除了 Visual Studio 项目默认命名空间

(注意:有一个 open a 勾选 on connect
https ://connect.microsoft.com/VisualStudio/feedback/details/668914/xaml- generated-code-uses-resource-uri-without- assembly-strong-name

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)

淡紫姑娘! 2024-08-12 05:30:43

我倾向于认为这可能是一个错误,或者至少是 XAML 工具中的一个缺陷。也许您应该在 Connect 上报告此问题。

我还没有尝试过,但这里有一些潜在的解决方法:

  1. 注入预构建步骤以自动修改 .g.cs 文件以使用 打包 URI,指定完整的程序集信息 (AssemblyShortName[;Version][;PublicKey];component/Path< /em>)
  2. 附加到 AppDomain.AssemblyResolve 以帮助 CLR 找到正确的程序集

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
七月上 2024-08-12 05:30:43

我在 VS2012 中一直在努力解决这个问题。我无法让 Riccardo 的解决方案在这种环境下工作。他的代码的这个变体……

_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));

确实解决了“无法定位资源”问题,但随后我在子元素中进一步遇到了以下错误:“无法注册命名对象。”无法在此范围内注册重复的名称“搜索”。”

亚伦·马丁的解决方案对我来说确实有效。抱歉,我无法发表评论或投票,但我没有得到代表。

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.

哎呦我呸! 2024-08-12 05:30:43

如果您的构建是自动化的,您还可以将 /p:AssemblyVersion=$version 参数传递给 msbuild 进程。

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

彻夜缠绵 2024-08-12 05:30:43

这段代码基于 Riccardo 的回答,在 VS2010 中为我工作。

首先,我定义了一个可以从 XAML 构造函数调用的加载器方法。

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);
        }
    }
}

然后,在每个 XAML 控件的构造函数中,我将 InitializeComponent() 替换为:

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

我确实注意到我的一些relativesource绑定停止工作,但我能够解决这个问题。

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.

伴随着你 2024-08-12 05:30:43

我们也遇到了同样的问题,但我们必须仅在解决方案中为某些特定项目设置程序集版本。

因为我喜欢像 user195275 推荐的那样为构建设置版本号的想法,所以我对如何为单个 csproj 文件执行此操作进行了一些研究。

因此,结合以下线程 How to read the assemblyversion from assemblyInfo.cs ? 我们提出了以下解决方案:

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

它的作用:它从 AssemblyInfo.cs 中解析版本号,并将其设置为属性,如 Aaron Martens 的答案中所示。这导致我们对版本号进行单点维护。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文