Пользовательский раздел конфигурации: не удалось загрузить файл или сборку
Мне очень тяжело пытаться получить доступ к пользовательскому разделу конфигурации в моем конфигурационном файле.
Файл конфигурации читается из DLL, которая загружается как плагин. Я создал Конфигурацию и необходимый код, используя надстройку Конструктора Разделов конфигурации VS.
Пространство имен - это "ImportConfiguration". Класс Configuration Section - это "ImportWorkflows". Сборка является ImportEPDMAddin.
XML:
<configSections>
<section name="importWorkflows" type="ImportConfiguration.ImportWorkflows, ImportEPDMAddin"/>
</configSections>
Всякий раз, когда я пытаюсь прочитать в конфиге, я получаю сообщение об ошибке:
Произошла ошибка при создании обработчика раздела конфигурации для importWorkflows: не удалось загрузить файл или сборку "ImportEPDMAddin.dll" или одну из ее зависимостей. Система не может найти указанный файл.
DLL не будет находиться в том же каталоге, что и исполняемый файл, так как программное обеспечение, которое загружает плагин, помещает DLL и его зависимости в свой собственный каталог. (Я не могу это контролировать.)
Я отредактировал код для экземпляра singleton следующим образом:
string path = System.Reflection.Assembly.GetCallingAssembly().CodeBase;
path = path.Replace("file:///", "");
System.Configuration.Configuration configuration = System.Configuration.ConfigurationManager.OpenExeConfiguration(path);
return configuration.GetSection(ImportWorkflowsSectionName) as ImportConfiguration.ImportWorkflows;
Я также пытался использовать простой NameValueFileSectionHandler, но получаю исключение, говорящее о том, что он не может загрузить файл или сборку "Система".
Я прочитал множество постов и статей в блоге, и звучит так, как будто можно прочитать файл конфигурации для DLL, но я просто не могу заставить его работать. Есть идеи? Благодарю.
7 ответов
К сожалению, вам нужно будет иметь ImportEPDMAddin
сборка, находящаяся в той же папке, что и ваш исполняемый файл, находящаяся в папке.Net framework, связанной с используемой вами платформой.Net (т.е. C:\Windows\Microsoft.NET\Framework\v2.0.50727), или зарегистрированной в глобальной Кеш сборок.
Единственный другой вариант - если вы знаете путь к сборке, которая содержит определяющий класс обработчика конфигурации, вы можете загрузить его без ссылки примерно так:
//Class global
private Assembly configurationDefiningAssembly;
protected TConfig GetCustomConfig<TConfig>(string configDefiningAssemblyPath,
string configFilePath, string sectionName) where TConfig : ConfigurationSection
{
AppDomain.CurrentDomain.AssemblyResolve += new
ResolveEventHandler(ConfigResolveEventHandler);
configurationDefiningAssembly = Assembly.LoadFrom(configDefiningAssemblyPath);
var exeFileMap = new ExeConfigurationFileMap();
exeFileMap.ExeConfigFilename = configFilePath;
var customConfig = ConfigurationManager.OpenMappedExeConfiguration(exeFileMap,
ConfigurationUserLevel.None);
var returnConfig = customConfig.GetSection(sectionName) as TConfig;
AppDomain.CurrentDomain.AssemblyResolve -= ConfigResolveEventHandler;
return returnConfig;
}
protected Assembly ConfigResolveEventHandler(object sender, ResolveEventArgs args)
{
return configurationDefiningAssembly;
}
Убедитесь, что вы обработали событие AssemblyResolve, так как это вызовет исключение без него.
В файле конфигурации вашего основного приложения добавьте следующее (где плагины - это папка для загрузки вашей сборки. Вы можете использовать несколько путей, разделенных точкой с запятой.
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath=".;.\Plugins"/>
</assemblyBinding>
</runtime>
Взято с http://msdn.microsoft.com/en-us/library/823z9h8w%28v=vs.90%29.aspx
Чтобы расширить превосходный ответ AJ, вот пользовательский класс, который поможет с издержками регистрации и удаления глобального события.
public sealed class AddinCustomConfigResolveHelper : IDisposable
{
public AddinCustomConfigResolveHelper(
Assembly addinAssemblyContainingConfigSectionDefinition)
{
Contract.Assert(addinAssemblyContainingConfigSectionDefinition != null);
this.AddinAssemblyContainingConfigSectionDefinition =
addinAssemblyContainingConfigSectionDefinition;
AppDomain.CurrentDomain.AssemblyResolve +=
this.ConfigResolveEventHandler;
}
~AddinCustomConfigResolveHelper()
{
this.Dispose(false);
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool isDisposing)
{
AppDomain.CurrentDomain.AssemblyResolve -= this.ConfigResolveEventHandler;
}
private Assembly AddinAssemblyContainingConfigSectionDefinition { get; set; }
private Assembly ConfigResolveEventHandler(object sender, ResolveEventArgs args)
{
// often the name provided is partial...this will match full or partial naming
if (this.AddinAssemblyContainingConfigSectionDefinition.FullName.Contains(args.Name))
{
return this.AddinAssemblyContainingConfigSectionDefinition;
}
return null;
}
}
Я бы предложил создать экземпляр в операторе using, например, так:
// you'll need to populate these two variables
var configuration = GetConfiguration();
var assembly = GetAssemblyContainingConfig();
using(new AddinCustomConfigResolveHelper(assembly))
{
return (MyConfigSection)configuration.GetSection("myConfigSection");
}
Пришлось использовать полностью определенную строку типа моей сборки модуля / плагина, которая находится в каталоге зондирования, чтобы его можно было найти. Используя EntityFramework в качестве примера...
Неправильно:
type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework"
Правильный
type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
Не могли бы вы проверить, правильно ли заданы пути зондирования в файле конфигурации вашего хост-приложения? Возможно, в текущем домене приложения не загружается необходимая ссылка.
Вы убедились, что DLL загружается первым? Возможно с Assembly.LoadFile("PATH")
?
Если вы не можете заставить классы в System.Configuration работать должным образом, вы всегда можете воспользоваться XmlDocument для ручного анализа файла конфигурации. Используйте XPath, чтобы упростить получение данных. Например (предполагая вашу переменную пути выше):
var document = new XmlDocument();
document.Load(path);
var node = document.SelectSingleNode("configuration/importWorkflows/add[@name='KEY']");
// Do whatever with node
Я попробовал ответ AJ с добавлением rileywhite, но обнаружил, что это не работает для меня.
В моем сценарии пользовательский класс ConfigurationSection уже находился в выполняющейся в данный момент сборке, и попытка его загрузки вызывает переполнение стека. Я также не хотел помещать это в GAC, хотя это действительно решало проблему, как об этом сообщает OP.
В итоге я обнаружил, что это работает достаточно хорошо для моей цели. Может быть, другие найдут это полезным:
public class CustomConfigurationSection : ConfigurationSection {
public CustomConfigurationSection()
{
var reader = XmlReader.Create(<path to my dll.config>);
reader.ReadToDescendant("CustomConfigurationSection");
base.DeserializeElement(reader,false);
}
// <rest of code>
}