1. ホーム
  2. c#

[解決済み] 実行時にデフォルトのapp.configを変更する

2022-06-18 21:29:52

質問

次のような問題があります。

私たちは、モジュール (アドオン) をロードするアプリケーションを持っています。これらのモジュールは、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とは異なる値が含まれていますが、同じ値が2回出力されます。

どのように解決するのですか?

リンク先の質問にあるハックは、設定システムを初めて使用する前に使用すれば動作します。それ以後は動作しません。

その理由は

クラスが存在し 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) を、アプリケーションの開始位置のどこかでusingを使わずに記述します。