実行時にデフォルトのapp.configを変更する


130

次の問題があります:
モジュールをロードする(アドオン)アプリケーションがあります。これらのモジュールでは、app.configにエントリが必要な場合があります(WCF構成など)。モジュールは動的に読み込まれるため、アプリケーションのapp.configファイルにこれらのエントリを含めたくありません。
私がしたいことは次のとおりです:

  • モジュールの構成セクションを組み込んだ新しいapp.configをメモリに作成します
  • アプリケーションにその新しいapp.configを使用するように伝えます

注:デフォルトのapp.configを上書きしたくない!

たとえばConfigurationManager.AppSettings、新しいファイルを使用できるように、透過的に機能する必要があります。

この問題の評価中に、私はここで提供されているものと同じソリューションを思いつきました:nunitでapp.configをリロードします
残念ながら、私はまだ通常のapp.configからデータを取得しているため、何も実行しないようです。

私はそれをテストするためにこのコードを使用しました:

Console.WriteLine(ConfigurationManager.AppSettings["SettingA"]);
Console.WriteLine(Settings.Default.Setting);

var combinedConfig = string.Format(CONFIG2, CONFIG);
var tempFileName = Path.GetTempFileName();
using (var writer = new StreamWriter(tempFileName))
{
    writer.Write(combinedConfig);
}

using(AppConfig.Change(tempFileName))
{
    Console.WriteLine(ConfigurationManager.AppSettings["SettingA"]);
    Console.WriteLine(Settings.Default.Setting);
}

同じ値を2回出力しcombinedConfigますが、通常のapp.config 以外の値が含まれています。


AppDomain適切な構成ファイルを使用してモジュールを個別にホストすることはオプションではありませんか?
ジョアン・アンジェロ

実際にはそうではありません。アプリケーションがモジュールと非常に頻繁に相互作用するため、多くのCross-AppDomain呼び出しが発生するためです。
Daniel Hilgarth、2011年

新しいモジュールをロードする必要があるときにアプリケーションを再起動するのはどうですか?
ジョアンアンジェロ

これは、ビジネス要件と連動しません。さらに、app.configを上書きすることはできません。ユーザーがそうする権利がないためです。
Daniel Hilgarth、2011年

プログラムファイル内ではなく、別のApp.configをロードするためにリロードします。Reload app.config with nunit設定が読み込まれる前にアプリケーションのエントリで使用された場合、ハッキングは機能する可能性があります。
ジョアン・アンジェロ

回答:


280

リンクされた質問のハックは、設定システムが最初に使用される前に使用された場合に機能します。その後は機能しなくなります。
理由:パスをキャッシュ
するクラスが存在ClientConfigPathsします。そのため、でパスを変更した後でも、SetDataキャッシュされた値がすでに存在するため、再読み取りは行われません。解決策はこれらも削除することです:

using System;
using System.Configuration;
using System.Linq;
using System.Reflection;

public abstract class AppConfig : IDisposable
{
    public static AppConfig Change(string path)
    {
        return new ChangeAppConfig(path);
    }

    public abstract void Dispose();

    private class ChangeAppConfig : AppConfig
    {
        private readonly string oldConfig =
            AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE").ToString();

        private bool disposedValue;

        public ChangeAppConfig(string path)
        {
            AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", path);
            ResetConfigMechanism();
        }

        public override void Dispose()
        {
            if (!disposedValue)
            {
                AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", oldConfig);
                ResetConfigMechanism();


                disposedValue = true;
            }
            GC.SuppressFinalize(this);
        }

        private static void ResetConfigMechanism()
        {
            typeof(ConfigurationManager)
                .GetField("s_initState", BindingFlags.NonPublic | 
                                         BindingFlags.Static)
                .SetValue(null, 0);

            typeof(ConfigurationManager)
                .GetField("s_configSystem", BindingFlags.NonPublic | 
                                            BindingFlags.Static)
                .SetValue(null, null);

            typeof(ConfigurationManager)
                .Assembly.GetTypes()
                .Where(x => x.FullName == 
                            "System.Configuration.ClientConfigPaths")
                .First()
                .GetField("s_current", BindingFlags.NonPublic | 
                                       BindingFlags.Static)
                .SetValue(null, null);
        }
    }
}

使い方は次のとおりです:

// the default app.config is used.
using(AppConfig.Change(tempFileName))
{
    // the app.config in tempFileName is used
}
// the default app.config is used.

アプリケーションのランタイム全体で使用されるapp.configを変更する場合は、アプリケーションAppConfig.Change(tempFileName)の最初のどこかに使用せずに配置します。


4
これは本当に、本当に素晴らしいです。これを投稿していただきありがとうございます。
user981225

3
@Danielそれは素晴らしかった-ApplicationSettingsBaseの拡張メソッドに組み込んだので、Settings.Default.RedirectAppConfig(path)を呼び出すことができます。できれば+2を差し上げます。
JMarsch 2013

2
@PhilWhittington:そうですね、そうです。
Daniel Hilgarth、2013

2
興味深いことに、ファイナライザが宣言されていないのにファイナライザを抑制する理由はありますか?
Gusdor 2014

3
それはさておき、プライベートフィールドにアクセスするためにリフレクションを使用することは現在機能する可能性がありますが、サポートされていないという警告を使用し、.NET Frameworkの将来のバージョンで機能しなくなる可能性があります。

10

ランタイムでConfigurationとAdd ConfigurationSectionを使用してみることができます

Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration(
                        new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config,
                        ConfigurationUserLevel.None
                        );

applicationConfiguration.Sections.Add("section",new YourSection())
applicationConfiguration.Save(ConfigurationSaveMode.Full,true);

編集:これは反射に基づいた解決策です(ただしあまりよくありません)

派生クラスを作成する IInternalConfigSystem

public class ConfigeSystem: IInternalConfigSystem
{
    public NameValueCollection Settings = new NameValueCollection();
    #region Implementation of IInternalConfigSystem

    public object GetSection(string configKey)
    {
        return Settings;
    }

    public void RefreshConfig(string sectionName)
    {
        //throw new NotImplementedException();
    }

    public bool SupportsUserConfig { get; private set; }

    #endregion
}

次にリフレクションを介してそれをプライベートフィールドに設定します ConfigurationManager

        ConfigeSystem configSystem = new ConfigeSystem();
        configSystem.Settings.Add("s1","S");

        Type type = typeof(ConfigurationManager);
        FieldInfo info = type.GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);
        info.SetValue(null, configSystem);

        bool res = ConfigurationManager.AppSettings["s1"] == "S"; // return true

これがどのように役立つかわかりません。これにより、で指定されたファイルにセクションが追加されfile_pathます。これは、デフォルトのapp.configを使用するConfigurationManager.GetSectionため、のユーザーがセクションを使用できるようにはなりませんGetSection
Daniel Hilgarth、2011年

既存のapp.configにセクションを追加できます。ちょうどこれを試してみました-私のために働きます
Stecya '27

私の質問からの引用:「注:デフォルトのapp.configを上書きしたくない!」
Daniel Hilgarth、2011年

5
どうしましたか?単純:プログラムは%ProgramFiles%にインストールされ、ユーザーは管理者ではないため、ユーザーには上書きする権利がありません。
Daniel Hilgarth、2011年

2
@Stecya:ご協力ありがとうございます。しかし、問題の実際の解決策については私の回答を参照してください。
Daniel Hilgarth、2011年

5

@Danielソリューションは問題なく動作します。詳細な説明のある同様のソリューションは、c-sharpコーナーにあります。 完全を期すために、私のバージョンを共有したいと思います:with using、およびビットフラグは省略されています。

using System;//AppDomain
using System.Linq;//Where
using System.Configuration;//app.config
using System.Reflection;//BindingFlags

    /// <summary>
    /// Use your own App.Config file instead of the default.
    /// </summary>
    /// <param name="NewAppConfigFullPathName"></param>
    public static void ChangeAppConfig(string NewAppConfigFullPathName)
    {
        AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", NewAppConfigFullPathName);
        ResetConfigMechanism();
        return;
    }

    /// <summary>
    /// Remove cached values from ClientConfigPaths.
    /// Call this after changing path to App.Config.
    /// </summary>
    private static void ResetConfigMechanism()
    {
        BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Static;
        typeof(ConfigurationManager)
            .GetField("s_initState", Flags)
            .SetValue(null, 0);

        typeof(ConfigurationManager)
            .GetField("s_configSystem", Flags)
            .SetValue(null, null);

        typeof(ConfigurationManager)
            .Assembly.GetTypes()
            .Where(x => x.FullName == "System.Configuration.ClientConfigPaths")
            .First()
            .GetField("s_current", Flags)
            .SetValue(null, null);
        return;
    }

4

誰かが興味を持っている場合は、Monoで機能する方法を次に示します。

string configFilePath = ".../App";
System.Configuration.Configuration newConfiguration = ConfigurationManager.OpenExeConfiguration(configFilePath);
FieldInfo configSystemField = typeof(ConfigurationManager).GetField("configSystem", BindingFlags.NonPublic | BindingFlags.Static);
object configSystem = configSystemField.GetValue(null);
FieldInfo cfgField = configSystem.GetType().GetField("cfg", BindingFlags.Instance | BindingFlags.NonPublic);
cfgField.SetValue(configSystem, newConfiguration);

3

Danielのソリューションは、以前にAppDomain.SetDataを使用したダウンストリームアセンブリでも機能するようですが、内部構成フラグをリセットする方法を認識していませんでした。

興味のある方のためにC ++ / CLIに変換

/// <summary>
/// Remove cached values from ClientConfigPaths.
/// Call this after changing path to App.Config.
/// </summary>
void ResetConfigMechanism()
{
    BindingFlags Flags = BindingFlags::NonPublic | BindingFlags::Static;
    Type ^cfgType = ConfigurationManager::typeid;

    Int32 ^zero = gcnew Int32(0);
    cfgType->GetField("s_initState", Flags)
        ->SetValue(nullptr, zero);

    cfgType->GetField("s_configSystem", Flags)
        ->SetValue(nullptr, nullptr);

    for each(System::Type ^t in cfgType->Assembly->GetTypes())
    {
        if (t->FullName == "System.Configuration.ClientConfigPaths")
        {
            t->GetField("s_current", Flags)->SetValue(nullptr, nullptr);
        }
    }

    return;
}

/// <summary>
/// Use your own App.Config file instead of the default.
/// </summary>
/// <param name="NewAppConfigFullPathName"></param>
void ChangeAppConfig(String ^NewAppConfigFullPathName)
{
    AppDomain::CurrentDomain->SetData(L"APP_CONFIG_FILE", NewAppConfigFullPathName);
    ResetConfigMechanism();
    return;
}

1

構成ファイルが「appSettings」のキー/値で記述されている場合、そのようなコードで別のファイルを読み取ることができます。

System.Configuration.ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = configFilePath;

System.Configuration.Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
AppSettingsSection section = (AppSettingsSection)configuration.GetSection("appSettings");

次に、section.SettingsをKeyValueConfigurationElementのコレクションとして読み取ることができます。


1
すでに述べたように、ConfigurationManager.GetSection作成した新しいファイルを読み取らせたいと思います。あなたの解決策はそれをしません。
Daniel Hilgarth、2011年

@ダニエル:なぜ?「configFilePath」には任意のファイルを指定できます。そのため、新しく作成したファイルの場所を知る必要があるだけです。私は何か見落としてますか ?または、本当に「ConfigurationManager.GetSection」を使用する必要がありますか?
ロン・

1
はい、あなたは何かを見逃しています:ConfigurationManager.GetSectionデフォルトのapp.configを使用します。で開いた設定ファイルは関係ありませんOpenMappedExeConfiguration
Daniel Hilgarth、2011年

1

すばらしい議論ですが、ResetConfigMechanismメソッドにコメントを追加して、メソッドのステートメント/呼び出しの背後にある魔法を理解しています。追加されたファイルパスの存在チェック

using System;//AppDomain
using System.Linq;//Where
using System.Configuration;//app.config
using System.Reflection;//BindingFlags
using System.Io;

/// <summary>
/// Use your own App.Config file instead of the default.
/// </summary>
/// <param name="NewAppConfigFullPathName"></param>
public static void ChangeAppConfig(string NewAppConfigFullPathName)
{
    if(File.Exists(NewAppConfigFullPathName)
    {
      AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", 
      NewAppConfigFullPathName);
      ResetConfigMechanism();
      return;
    }
}

/// <summary>
/// Remove cached values from ClientConfigPaths.
/// Call this after changing path to App.Config.
/// </summary>
private static void ResetConfigMechanism()
{
    BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Static;
      /* s_initState holds one of the four internal configuration state.
          0 - Not Started, 1 - Started, 2 - Usable, 3- Complete

         Setting to 0 indicates the configuration is not started, this will 
         hint the AppDomain to reaload the most recent config file set thru 
         .SetData call
         More [here][1]

      */
    typeof(ConfigurationManager)
        .GetField("s_initState", Flags)
        .SetValue(null, 0);


    /*s_configSystem holds the configuration section, this needs to be set 
        as null to enable reload*/
    typeof(ConfigurationManager)
        .GetField("s_configSystem", Flags)
        .SetValue(null, null);

      /*s_current holds the cached configuration file path, this needs to be 
         made null to fetch the latest file from the path provided 
        */
    typeof(ConfigurationManager)
        .Assembly.GetTypes()
        .Where(x => x.FullName == "System.Configuration.ClientConfigPaths")
        .First()
        .GetField("s_current", Flags)
        .SetValue(null, null);
    return;
}

0

ダニエル、可能であれば他の構成メカニズムを使用してみてください。環境/プロファイル/グループに応じて異なる静的/動的構成ファイルが存在するこのルートを通過してきたため、最終的にはかなり厄介なものになりました。

クライアントからWebサービスURLを1つだけ指定し、クライアントの詳細に応じて(グループ/ユーザーレベルのオーバーライドがある場合)、必要なすべての構成をロードする、ある種のプロファイルWebサービスを試すことができます。また、MS Enterprise Libraryの一部を使用しています。

それはあなたがクライアントと一緒に設定をデプロイせず、あなたはクライアントとは別にそれを管理することができました


3
ご回答有難うございます。ただし、これの理由はすべて、構成ファイルの配布を回避するためです。モジュールの構成の詳細は、データベースからロードされます。しかし、モジュール開発者にデフォルトの.NET構成メカニズムの快適さを提供したいので、これらのモジュール構成を実行時に1つの構成ファイルに組み込み、これをデフォルトの構成ファイルにしたいと考えています。理由は簡単です。app.configを使用して構成できるライブラリが多数存在する(たとえば、WCF、EntLib、EFなど)です。別の構成メカニズムを導入する場合、構成は(続き)
Daniel Hilgarth、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.