Конфигурация .NET: раздел Configuration.GetSection исключает возможность обнаружения сборки - программирование
Подтвердить что ты не робот

Конфигурация .NET: раздел Configuration.GetSection исключает возможность обнаружения сборки

Я создал настраиваемый раздел конфигурации для DLL плагина, который хранит XML файл .config в отдельном файле (из основного исполняемого приложения).

Здесь образец пользовательского класса раздела:

using System;   
using System.Configuration;

namespace PluginFramework.MyConfiguration
{

public class MyConfigurationSettings : ConfigurationSection
{
    private Configuration _Config = null;

    #region ConfigurationProperties     
    /// <summary>
    /// A custom XML section for an application configuration file.
    /// </summary>
    [ConfigurationProperty("MyProjects", IsDefaultCollection = true)]
    public MyProjectConfigurationCollection MyProjects
    {
        get { return (MyProjectConfigurationCollection) base["MyProjects"]; }
    }

    // ...
    #endregion

    /// <summary>
    /// Private Constructor used by our factory method.
    /// </summary>
    private MyConfigurationSettings () : base () {
        // Allow this section to be stored in user.app. By default this is forbidden.
        this.SectionInformation.AllowExeDefinition =
        ConfigurationAllowExeDefinition.MachineToLocalUser;
    }

    // ...

    #region Static Members  
    /// <summary>
    /// Gets the current applications &lt;MyConfigurationSettings&gt; section.
    /// </summary>
    /// <param name="ConfigLevel">
    /// The &lt;ConfigurationUserLevel&gt; that the config file
    /// is retrieved from.
    /// </param>
    /// <returns>
    /// The configuration file &lt;MyConfigurationSettings&gt; section.
    /// </returns>
    public static MyConfigurationSettings GetSection (ConfigurationUserLevel ConfigLevel) 
    {
        string appDataPath = System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
        string localDataPath = System.Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
        System.Configuration.ExeConfigurationFileMap exeMap = new ExeConfigurationFileMap();
        exeMap.ExeConfigFilename = System.IO.Path.Combine(appDataPath, @"MyCompany\MyPluginApp\Default.config");
        exeMap.RoamingUserConfigFilename = System.IO.Path.Combine(appDataPath, @"MyCompany\MyPluginApp\Roaming.config");
        exeMap.LocalUserConfigFilename = System.IO.Path.Combine(localDataPath, @"MyCompany\MyPluginApp\Local.config");

        System.Configuration.Configuration Config = ConfigurationManager.OpenMappedExeConfiguration(exeMap,ConfigLevel);
        MyConfigurationSettings myConfigurationSettings = null;

        try {
            myConfigurationSettings = (MyConfigurationSettings)Config.GetSection("MyConfigurationSettings");
        } 
        catch (System.Exception ex) {
            // ConfigurationErrorsException caught here ...
        }
        if (myConfigurationSettings == null) {
            myConfigurationSettings = new MyConfigurationSettings();
            Config.Sections.Add("MyConfigurationSettings", myConfigurationSettings);                    }
        } 
        if(myConfigurationSettings != null) {
            myConfigurationSettings._Config = Config;
        }

        return myConfigurationSettings;
    }       
    #endregion
}
} // PluginFramework.MyConfiguration

XML.config, сгенерированный при сохранении в первый раз, выглядит следующим образом:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <!-- The exception complains about the following line (assembly attributes are compliant): -->
        <section name="MyConfigurationSettings" type="PluginFramework.MyConfiguration.MyConfigurationSettings, PluginFramework, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" allowDefinition="Everywhere" allowExeDefinition="MachineToLocalUser" />
    </configSections>
    <MyConfigurationSettings>
        <!-- Config properties are serialized fine according MyConfigurationSettings 
             properties marked with the ConfigurationProperty attribute ... -->
        <MyProjects>
            <MyProjectConfiguration GUID="{4307AC92-8180-4686-9322-830312ED59AB}">
                <!-- ... more complex configuration elements -->
            </MyProjectConfiguration>
        </MyProjects>
    </MyConfigurationSettings>
</configuration>

Когда этот XML пытается загрузить с помощью Config.GetSection() при последующих запусках, я поймаю ConfigurationErrorsException в строке, помеченной в примере XML, заявив, что сборка MyPlugin или одна из ее зависимостей не может быть (пожалуйста, простите, что я не отправляю оригинальное сообщение об исключении, но у меня есть только на немецком языке, и сомневаюсь, что этот текст будет полезен здесь). Внутреннее исключение исходит от System.IO при попытке загрузить сборку и получить отражение для разрешения типа класса MyConfigurationSettings.

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

Следующая диаграмма UML иллюстрирует отношения нескольких компонентов: Main App plugin and framework components

Оглядываясь немного вокруг этой проблемы, у меня возникает ощущение, что нужно сильное имя (знак) сборки, экспортирующей класс MyConfigurationSettings (т.е. PluginFramework) и зарегистрировать его с помощью GAC. Я еще не пробовал этого и хотел бы избежать этого шага по нескольким причинам (прежде чем знать, может ли это даже помочь, и это единственный выбор для решения проблемы).

Итак, вот вопросы (извините, что я размещаю на самом деле 4 вопроса здесь, но они так сильно связаны, что не имеет смысла создавать для них отдельные SO-вопросы).

  • Могу ли я решить проблему сбоя локации, решив называть эту сборку и регистрировать ее с помощью GAC?

  • Похоже, что сборка, на которую жалуется управление конфигурацией, гарантированно загружается (поскольку она сама вызывает Configuration.GetSection()).
    Может ли быть способ зарегистрировать сборку или соответствующие типы/сериализаторы типа конфигурации явно с классом ConfigurationManager или Confguration?

  • Мне также интересна дополнительная информация о комментарий Hans Passant, в котором упоминается, что это может быть проблемой, вызванной тем, как загружается (первичная) сборка из основное приложение. У меня нет контроля над этим механизмом, и если это вызывает поведенческое поведение, я бы хотел знать, есть ли разумное решение?

  • Другая идея (если что-либо из вышеперечисленного не может показать способ) заключается в том, чтобы полностью управлять XML-форматом конфигурации (используя поддержку де-/сериализации XML) и откуда загрузить и слить файлы конфигурации. Если это наиболее подходящий вариант, может ли кто-нибудь дать хорошие указатели, как это сделать эффективно (наименее необходимый код для управления путями и слияния)?

Update:
Поскольку никто, кажется, не в состоянии дать больше информации об этом вопросе (ответах), на самом деле я не догоняю меня дальше), я перехожу к варианту с 4., делая все это вручную.

4b9b3361

Ответ 1

Я тоже это пробовал, но у меня так и не получилось так работать. Я просто понял, что загрузка .config автоматически не работает на .dll только для .exe. Затем я сдался и решил, что будет проще загружать файл .config вручную. Вы можете увидеть полный код здесь: https://github.com/GeertBellekens/Enterprise-Architect-Toolpack/blob/master/EANavigator/NavigatorSettings.cs Это наиболее важная часть:

public NavigatorSettings() {
     Configuration roamingConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoaming);

     // the roamingConfig now get a path such as C:\Users\<user>\AppData\Roaming\Sparx_Systems_Pty_Ltd\DefaultDomain_Path_2epjiwj3etsq5yyljkyqqi2yc4elkrkf\9,_2,_0,_921\user.config
     // which I don't like. So we move up three directories and then add a directory for the EA Navigator so that we get
     // C:\Users\<user>\AppData\Roaming\GeertBellekens\EANavigator\user.config
     string configFileName =  System.IO.Path.GetFileName(roamingConfig.FilePath);
     string configDirectory = System.IO.Directory.GetParent(roamingConfig.FilePath).Parent.Parent.Parent.FullName;

     string newConfigFilePath = configDirectory + @"\Geert Bellekens\EANavigator\" + configFileName;
     // Map the roaming configuration file. This
     // enables the application to access 
     // the configuration file using the
     // System.Configuration.Configuration class
     ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
     configFileMap.ExeConfigFilename = newConfigFilePath;       

     // Get the mapped configuration file.
     currentConfig = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
     // merge the default settings
     this.mergeDefaultSettings();
 }

Доступ к свойству конфигурации:

public bool trackSelectedElement
{
    get {
        bool result;
        if(bool.TryParse(this.currentConfig.AppSettings.Settings["trackSelectedElement"].Value, out result)) {
            return result;
        }
        else {
            return true;
        }
    }
    set {
        this.currentConfig.AppSettings.Settings["trackSelectedElement"].Value = value.ToString();
    }
}

Ответ 2

То, что вы пытаетесь сделать, не поддерживается .NET Framework.

1st - имеет смысл, что ваш plugin.dll настроен для используемого им хост-приложения (.exe или web) (почему он настраивается)

Поддержка 2-го файла конфигурации - наследование (например: machine.config → applicationHost.config → web.config). Это то, для чего они предназначены. В этом случае ваша конфигурация вне пути не будет работать должным образом.

Итак, если вам нужна настраиваемая конфигурация для части приложения или плагина, не следуя концепциям .config, сделайте там стандартный XML файл или jsonconfig и параметры загрузки.

Ответ 3

@г-makulik

Здесь у меня есть рабочая копия того, что было сделано в реальной среде и доказано, что она работает.

В файле App.config:

<configSections>
    <sectionGroup name="mySectionGroupName">
        <section name="mySectionName" type="MyNamespace.MySectionHandler,MyNamespace" />
    </sectionGroup>
</configSections>
....
<mySectionGroupName>
    <mySectionName>
        <add key="MyKey" value="MyKeyValue" />
    </mySectionName>
</mySectionGroupName>

В классе, в котором вы используете config:

....
Hashtable ht = ConfigurationManager.GetSection("mySectionGroupName/mySectionName") as Hashtable; 
// when you call this, your handler will do what you want in there
string keyVal = ht["MyKey"] as String;
....

Класс, отвечающий за управление конфигурацией:

public class MySectionHandler : DictionarySectionHandler 
{
    public override object Create(object parent, object context, XmlNode section) 
    {
        // here do what you want with the value of "MyKey" - "MyKeyValue"
    }
}

Надеюсь, это поможет

Ответ 4

У меня такая же проблема и пока не найдено полностью удовлетворительного решения. Наши нагрузки приложений скомпилированы со ссылкой на класс раздела конфигурации, определенный в выделенной сборке. Приложение связано с сильно именованной сборкой, но позже, когда загрузчик конфигурации пытается прочитать конфигурацию, следы слияния показывают, что он пытается загрузить слабое имя той же сборки. По какой-то причине .net не видит, что это одна и та же сборка и генерирует исключение System.IO.FileNotFound. На рабочем решении в моем случае стоит ссылаться на сильные имена в конфигурации.

Я также заметил странное поведение: после того, как сборка загружена загрузчиком конфигурации .net с сильным именем, дальнейшие ссылки со слабым именем действительно успешны! По какой-то причине структура "запоминает", что слабое имя ссылается на одну и ту же сборку.

Любые новости из вопроса на вашем конце были бы интересными!

Ответ 5

Добавить обработчик событий для AppDomain.CurrentDomain.AssemblyResolve. Это должно работать для option 2.