Конфигурация .NET AssemblyBinding игнорируется в файле machine.config
-
10-07-2019 - |
Вопрос
У меня есть ситуация, когда мне нужно иметь возможность загружать сборки в GAC на основе их частичных имен. Для этого я добавил в свой файл app.config следующее:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<qualifyAssembly partialName="MyAssembly"
fullName= "MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef"/>
</assemblyBinding>
</runtime>
Это работает именно так, как я хочу. Однако, если я помещаю тот же элемент в мой файл machine.config, он, кажется, игнорируется, и я получаю FileNotFoundExceptions при попытке загрузить MyAssembly.
Ниже приведен журнал привязки сборки, когда элемент находится в моем app.config, и привязка выполнена успешно:
LOG: This bind starts in default load context. LOG: Using application configuration file: C:\Documents and Settings\jon_scheiding\My Documents\Source\Testing\Test Projects 1\Cmd\bin\Debug\Testers.Cmd.vshost.exe.config LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config. LOG: Partial reference qualified from config file. New reference: MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef. LOG: Post-policy reference: MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=0123456789abcdef LOG: Found assembly by looking in the GAC. LOG: Binding succeeds. Returns assembly from C:\WINDOWS\assembly\GAC_MSIL\MyAssembly\1.0.0.0__b20f4683c1030dbd\MyAssembly.dll. LOG: Assembly is loaded in default load context.
Сравните это с журналом, когда моя конфигурация находится в machine.config и сбой привязки:
LOG: This bind starts in default load context. LOG: Using application configuration file: C:\Documents and Settings\jon_scheiding\My Documents\Source\Testing\Test Projects 1\Cmd\bin\Debug\Testers.Cmd.vshost.exe.config LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config. LOG: Policy not being applied to reference at this time (private, custom, partial, or location-based assembly bind). LOG: Attempting download of new URL file:///C:/Documents and Settings/jon_scheiding/My Documents/Source/Testing/Test Projects 1/Cmd/bin/Debug/MyAssembly.DLL. LOG: Attempting download of new URL file:///C:/Documents and Settings/jon_scheiding/My Documents/Source/Testing/Test Projects 1/Cmd/bin/Debug/MyAssembly/MyAssembly.DLL. LOG: Attempting download of new URL file:///C:/Documents and Settings/jon_scheiding/My Documents/Source/Testing/Test Projects 1/Cmd/bin/Debug/MyAssembly.EXE. LOG: Attempting download of new URL file:///C:/Documents and Settings/jon_scheiding/My Documents/Source/Testing/Test Projects 1/Cmd/bin/Debug/MyAssembly/MyAssembly.EXE. LOG: All probing URLs attempted and failed.
Проблема, похоже, в четвертой строке, " Политика не применяется к ссылке в настоящее время. " Однако я могу найти очень мало документации о том, что означает это сообщение, или о том, как к нему обратиться.
Как мне заставить фреймворк распознавать мой < runtime > Элемент? р>
Заранее спасибо!
Решение
Фреймворк не читает конфигурацию qualifyAssebmly из machine.config, он только читает ее из файла конфигурации вашего приложения. Р>
Платформа распознает ваш элемент runtim, однако не распознает элемент qualifyAssembly. Р>
Другие советы
Я вернулся к этой необходимости гораздо позже, так как у нас есть несколько сборок в GAC, и такие вещи, как NHibernate, необходимы для их решения. Вместо добавления всех этих элементов qualifyAssembly в каждый файл app.config, следующий код позволяет нам добавить их в наш machine.config.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.XPath;
namespace MyNamespace {
/// <summary>
/// Implements <see cref="IDisposable"/> to provide a scope for resolving
/// assemblies described in the machine.config file with
/// <qualifyAssembly> elements.
/// </summary>
/// <remarks>
/// Because the framework only respects <qualifyAssembly> at the
/// application configuration level, this class provides similar
/// functionality for this element at the machine configuration level.
/// You can wrap a new instance of this class in a <b>using</b> statement
/// to get resolution within a specific scope; or, you can call the
/// <see cref="AssemblyResolver.Attach()"/> method to get
/// resolution for the lifetime of the current <see cref="AppDomain"/>.
/// </remarks>
public sealed class AssemblyResolver : IDisposable {
#region Private fields and implementation
private static Dictionary<string, string> _qualifiedNames;
private static Assembly ResolveAssembly(object sender, ResolveEventArgs args) {
if(_qualifiedNames == null) {
//
// Lazily initialize short/long name mappings.
//
_qualifiedNames = BuildQualifiedNameList();
}
if(!_qualifiedNames.ContainsKey(args.Name)) {
return null;
}
try {
return Assembly.Load(_qualifiedNames[args.Name]);
}
catch(FileNotFoundException) {
//
// TODO: Should this exception be propogated?
// It probably should not be hidden from the consumer
// since it likely indicates a configuration error.
//
return null;
}
}
private static Dictionary<string, string> BuildQualifiedNameList() {
var dict = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
//
// Get runtime XML data
//
var xml = GetConfigXml(GetMachineRuntimeSection());
if(xml == null) {
return dict;
}
//
// Iterate the qualifyAssembly elements and register in the list
//
var navigator = xml.CreateNavigator();
foreach(XPathNavigator qualifyAssembly in navigator.Select("runtime/asm:assemblyBinding/asm:qualifyAssembly", CreateNamespaceManager())) {
dict.Add(
qualifyAssembly.GetAttribute("partialName", string.Empty),
qualifyAssembly.GetAttribute("fullName", string.Empty));
}
return dict;
}
private static ConfigurationSection GetMachineRuntimeSection() {
System.Configuration.Configuration machineConfig = ConfigurationManager.OpenMachineConfiguration();
return machineConfig.GetSection("runtime") as ConfigurationSection;
}
private static IXPathNavigable GetConfigXml(ConfigurationSection runtimeSection) {
var ignoreSection = runtimeSection as IgnoreSection;
if(ignoreSection == null) {
return null;
}
//
// Cheat via Reflection to get the XML content of the config
// section.
//
FieldInfo field = typeof(IgnoreSection).GetField("_rawXml", BindingFlags.Instance | BindingFlags.NonPublic);
string rawXml = (string)field.GetValue(ignoreSection);
return new XPathDocument(new StringReader(rawXml));
}
private static IXmlNamespaceResolver CreateNamespaceManager() {
var nsmgr = new XmlNamespaceManager(new NameTable());
nsmgr.AddNamespace("asm", "urn:schemas-microsoft-com:asm.v1");
return nsmgr;
}
#endregion
/// <summary>
/// Creates a new <see cref="AssemblyResolver"/>.
/// </summary>
public AssemblyResolver() {
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveAssembly);
}
/// <summary>
/// Removes the current <see cref="AssemblyResolver"/>
/// from the <see cref="AppDomain"/>.
/// </summary>
public void Dispose() {
AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(ResolveAssembly);
GC.SuppressFinalize(this);
}
/// <summary>
/// Provides qualified assembly resolution for the lifetime of the
/// current <see cref="AppDomain"/>.
/// </summary>
public static void Attach() {
Attach(AppDomain.CurrentDomain);
}
/// <summary>
/// Provides qualified assembly resolution for the lifetime of an
/// <see cref="AppDomain"/>.
/// </summary>
/// <param name="appDomain">
/// The <see cref="AppDomain"/> to service.
/// </param>
public static void Attach(AppDomain appDomain) {
appDomain.AssemblyResolve += new ResolveEventHandler(ResolveAssembly);
}
}
}
В большинстве случаев мы ограничиваем область его использования, используя его следующим образом:
using(new AssemblyResolver()) {
//
// NHibernate initialization or whatever
//
}
Его также можно использовать в коде запуска приложения или в global.asax и сохранять в течение всего жизненного цикла приложения, например так:
AssemblyResolver.Attach();
//
// or
//
AssemblyResolver.Attach(AppDomain.CurrentDomain);