Пользовательский раздел конфигурации: не удалось загрузить файл или сборку

Мне очень тяжело пытаться получить доступ к пользовательскому разделу конфигурации в моем конфигурационном файле.

Файл конфигурации читается из 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>
}
Другие вопросы по тегам