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

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

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

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

/// <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» (например, «Невозможно найти представления ресурса / панели обслуживания» .xaml '.

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

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

Обратите внимание, насколько я понимаю, это ошибка WPF. Чтобы избежать этого, не следует использовать изоляцию домена приложений.


person Phil    schedule 21.09.2009    source источник
comment
Привет, Фил, я столкнулся с той же проблемой. Удалось ли вам найти какое-нибудь решение?   -  person Manish Basantani    schedule 04.03.2011
comment
Кто-нибудь сталкивался с подобной проблемой с настраиваемыми элементами управления WPF с использованием тем (через словари ресурсов)? Если да, какое решение для этого?   -  person akjoshi    schedule 17.10.2011
comment
Есть ли в этом прогресс? Конечно, люди могут создавать библиотеки WPF без этой проблемы. Мне нужно, чтобы у меня была именно эта проблема.   -  person CathalMF    schedule 25.06.2019


Ответы (7)


Я столкнулся с той же проблемой, и это может быть возможным решением

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

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

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

person Riccardo    schedule 14.06.2011
comment
в [[[namespace]]] это не пространство имен, а расположение относительно проекта страницы, которую вы пытаетесь загрузить (которые обычно совпадают с пространством имен без корня) - person L.Trabacchin; 01.12.2014

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

<PropertyGroup>
  <AssemblyVersion>1.0.0.0</AssemblyVersion>
  <AssemblyPublicKeyToken>[YOUR_PUBLIC_KEY_TOKEN]</AssemblyPublicKeyToken>
</PropertyGroup>
person Aaron Marten    schedule 17.05.2012
comment
Откуда ты знал это? Это так .. эзотерично. это даже не официально поддерживается, но работает! - person user195275; 01.11.2014
comment
Мне не нравится это решение, что, если я изменю версию (без автоматического рефакторинга) или у меня будет другой ключ на моем отладочном компьютере и на сервере сборки? - person L.Trabacchin; 26.11.2014
comment
Я трудился над этим вопросом больше недели и очень сильно страдал! Ваш ответ подействовал на меня как шарм и очень помог! Спасибо!! (также обратите внимание на этот сайт, который процитировал эту ветку) - person marc wellman; 01.02.2016
comment
У меня была эта проблема, которая проявлялась как 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). Оказывается, я перезаписывал свойство AssemblyVersion. - person rossng; 21.01.2019

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

Я не пробовал, но вот несколько возможных обходных путей:

  1. Добавьте шаг перед сборкой для автоматического изменения файлов .g.cs для использования URI упаковки, которые указывают полную информацию о сборке (AssemblyShortName [; Версия] [; PublicKey]; компонент / Путь)
  2. Присоединитесь к AppDomain.AssemblyResolve, чтобы помочь CLR найти нужную сборку.
person Kent Boogaart    schedule 21.09.2009
comment
Спасибо, Кент, хотя я надеялся, что будет какой-то встроенный способ справиться с этим! (хотя я не нашел). Я не думаю, что №2 поможет - почти уверен, что запрос разрешения сборки будет иметь только слабое имя, поскольку это то, что указывает URI, и тогда я не знаю, какую сборку следует использовать. №1 может сработать, хотя явно некрасиво. Кстати, отличная работа над фермой. Используя его в моем текущем проекте. - person Phil; 21.09.2009
comment
присоединение к assemblyresolve, незнание того, какую из них вы хотите загрузить, мне не кажется полезным, возможно, вы можете использовать запрашивающую сборку, если она указана - person L.Trabacchin; 26.11.2014

Я боролся с этим в VS2012. Я не мог заставить решение Риккардо работать в этой среде. Этот вариант его кода ...

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

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

Решение Аарона Мартена действительно работает для меня. Извините, я не могу комментировать или проголосовать, но у меня нет представителя.

person johndsamuels    schedule 02.04.2013

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

person user195275    schedule 01.11.2014
comment
Я трудился над этим вопросом больше недели и очень сильно страдал! Ваш ответ подействовал на меня как шарм и очень помог! Спасибо!! (также обратите внимание на этот сайт, на котором цитируется ваш ответ) - person marc wellman; 01.02.2016

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

person tdr    schedule 16.11.2016
comment
При этом я получаю исключение: не могу найти ресурс basicwizard.xaml. где basicwizard - мой класс окна - person chincheta73; 14.08.2018

У нас тоже была такая же проблема, но нам нужно было установить версию сборки для некоторых конкретных проектов только в нашем решении.

Поскольку мне понравилась идея установить номер версии для сборки, как рекомендует user195275, я провел небольшое исследование того, как это сделать для одного файла csproj.

Итак, в сочетании со следующим потоком Как читать версию сборки из 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 и устанавливает его как Свойство, как в ответе Аарона Мартенса. Это приводит к единой точке обслуживания номера версии для нас.

person eberthold    schedule 16.10.2018