Как заставить WPF использовать URI ресурсов, которые используют сильное имя сборки? Argh!

Хорошо, это действительно раздражает, я уже заметил, что код, созданный WPF для загрузки ресурсов XAML, по-видимому, не использовал сильные имена и поэтому может быть проблематичным для сценариев, в которых вам необходимо поддерживать бок о бок версии сборки WPF.

Это оказалось так, и теперь это вызывает у меня проблемы. У меня есть подключаемая система, которая должна поддерживать бок о бок установку плагинов, которые отличаются только номерами их версий (их версиями сборки). Это, конечно, может поддерживаться .NET, поскольку сборки имеют разные идентификаторы, даже если они имеют одинаковое имя файла DLL при условии, что они сильно пронумерованы и имеют другой открытый/закрытый ключ или имеют другой номер версии сборки.

Теперь, если мы посмотрим на код, созданный для окон и usercontrols визуальной студией, мы увидим в автогенерированном файле следующее:

/// <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, который не указывает сильное имя или версию сборки, которая содержит ресурс xaml.

Я подумал, что, возможно, LoadComponent проверит идентификатор вызывающей сборки и будет использовать его как открытый ключ и информацию о версии, так и, возможно, проверить личность сборки, которая содержит тип для параметра 'this'.

Похоже, что это не так - если у вас есть две сборки с разными номерами версий (но одно и то же имя файла), тогда вы можете получить IOException с сообщением "Невозможно найти ресурс X" (для примера выше "Невозможно найти ресурс", просмотров /servicepanelui.xaml.

Хуже того, я уверен, что это также означает, что сбои с тем же именем файла, но с другим открытым/закрытым ключом, то есть от разных издателей, также приведут к этой ошибке.

Итак, кто-нибудь знает, как обойти это? Как сделать WPF прочным именем.

Заметьте, насколько мне известно, это ошибка WPF. Вам не нужно использовать изоляцию Appdomain, чтобы избежать этого.

Ответ 1

Вы можете установить в файле проекта следующие изменения URI в сгенерированном коде:

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

Ответ 2

Я склонен согласиться с тем, что это, вероятно, ошибка или, по крайней мере, недостаток в инструментах XAML. Возможно, вам стоит сообщить об этом Connect.

Я не пробовал, но вот пара возможных обходных решений:

  • Внедрить шаг предварительной сборки для автоматического изменения файлов .g.cs, чтобы использовать URI пакета, которые определяют полную информацию о сборке (AssemblyShortName [; версия] [; ОткрытыйКлюч]; компонент/Путь)
  • Прикрепите к AppDomain.AssemblyResolve, чтобы помочь CLR найти нужную сборку

Ответ 3

У меня возникла такая же проблема, и это может быть возможным решением

каждый раз, когда элемент управления создается с помощью .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))

где as [[[namespace]]] введите полное пространство имен класса, за исключением пространства имен по умолчанию для проектной студии Visual Studio

(Примечание: есть открытый галочкой на подключении https://connect.microsoft.com/VisualStudio/feedback/details/668914/xaml-generated-code-uses-resource-uri-without-assembly-strong-name)

Ответ 4

Я столкнулся с этим в 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));

... разрешил проблему "не найти ресурс", но затем я немного ударил следующую ошибку в дочернем элементе: "Не удалось зарегистрировать именованный объект. Невозможно зарегистрировать дублирующее имя" поиск" в этой области.

Решение Aaron Marten работает для меня. Извините, я не могу комментировать или продвигать, но у меня нет репутации.

Ответ 5

Вы также можете передать параметр /p: AssemblyVersion = $version в процесс msbuild, если ваши сборки автоматизированы.

Ответ 6

Этот код, основанный на ответе Риккардо, работал на меня в VS2010.

Сначала я определил метод loader, который я могу вызвать из своего конструктора 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 перестали работать, но я смог обойти это.