Çalışma zamanında varsayılan app.config'i değiştirin


130

Şu problemim var:
Modülleri (eklentileri) yükleyen bir uygulamamız var. Bu modüllerin app.config dosyasında girişlere ihtiyacı olabilir (örn. WCF yapılandırması). Modüller dinamik olarak yüklendiğinden, bu girdilerin uygulamamın app.config dosyasında olmasını istemiyorum.
Yapmak istediğim şey şu:

  • Modüllerden yapılandırma bölümlerini içeren bellekte yeni bir app.config oluşturun
  • Uygulamama bu yeni app.config'i kullanmasını söyle

Not: Varsayılan app.config'in üzerine yazmak istemiyorum!

Şeffaf bir şekilde çalışmalıdır, böylece örneğin ConfigurationManager.AppSettingsbu yeni dosyayı kullanır.

Bu problemle ilgili değerlendirmem sırasında, burada sağlananla aynı çözümü buldum: app.config dosyasını nunit ile yeniden yükleyin .
Ne yazık ki hiçbir şey yapmıyor gibi görünüyor, çünkü verileri hala normal app.config'den alıyorum.

Bu kodu test etmek için kullandım:

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);
}

combinedConfigNormal app.config değerinden başka değerler içermesine rağmen, aynı değerleri iki kez yazdırır .


Modülleri AppDomainuygun yapılandırma dosyasıyla ayrı olarak barındırmak bir seçenek değil mi?
João Angelo

Gerçekten değil, çünkü bu çok sayıda Cross-AppDomain çağrısına neden olur, çünkü uygulama modüllerle oldukça yoğun bir şekilde etkileşime girer.
Daniel Hilgarth

Yeni bir modül yüklenmesi gerektiğinde uygulamanın yeniden başlatılmasına ne dersiniz?
João Angelo

Bu, iş gereksinimleri ile birlikte çalışmaz. Ayrıca, app.config dosyasının üzerine yazamıyorum çünkü kullanıcının bunu yapma hakkı yok.
Daniel Hilgarth

Program dosyalarındaki değil, farklı bir App.config'i yüklemek için yeniden yüklüyor olacaksınız. Hack in Reload app.config with nunit, herhangi bir yapılandırma yüklenmeden önce uygulama girişinde kullanılırsa işe yarayabilir.
João Angelo

Yanıtlar:


280

Bağlantılı sorudaki hack, yapılandırma sistemi ilk kez kullanılmadan önce kullanılırsa çalışır. Bundan sonra artık çalışmıyor.
Nedeni: Yolları önbelleğe alan
bir sınıf var ClientConfigPaths. Bu nedenle, yolu ile değiştirdikten sonra bile SetData, önbelleğe alınmış değerler zaten mevcut olduğundan yeniden okunmaz. Çözüm, bunları da kaldırmaktır:

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);
        }
    }
}

Kullanım şu şekildedir:

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

Uygulamanızın tüm çalışma zamanı için kullanılan app.config dosyasını değiştirmek istiyorsanız, uygulamanızın AppConfig.Change(tempFileName)başında herhangi bir yere kullanmadan koyun .


4
Bu gerçekten, gerçekten mükemmel. Bunu gönderdiğiniz için çok teşekkür ederim.
user981225

3
@Daniel Bu harikaydı - ApplicationSettingsBase için bir exentension yönteminde çalıştım, böylece Settings.Default.RedirectAppConfig (yol) 'u çağırabilirim. Elimde olsa sana +2 veririm!
JMarsch

2
@PhilWhittington: Ben de bunu söylüyorum, evet.
Daniel Hilgarth

2
ilgi dışı, kesinleştiriciyi bastırmak için herhangi bir sebep var mı, kesinleştirici beyan edilmemiş mi?
Gusdor

3
Bunun yanı sıra, özel alanlara erişmek için yansıma kullanmak artık işe yarayabilir, ancak desteklenmediğini ve .NET Framework'ün gelecek sürümlerinde bozulabileceğini belirten bir uyarı kullanabilir.

10

Çalışma zamanında Configuration ve Add ConfigurationSection kullanmayı deneyebilirsiniz.

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

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

DÜZENLEME: İşte yansımaya dayalı çözüm (yine de çok hoş değil)

Türetilen sınıf oluştur 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
}

sonra yansıma yoluyla özel alana ayarlayın 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

Bunun bana nasıl yardımcı olacağını anlamıyorum. Bu, ile belirtilen dosyaya bir bölüm ekleyecektir file_path. Varsayılan app.config kullandığından bu ConfigurationManager.GetSection, bölümü kullanıcılarının GetSectionkullanımına açmaz.
Daniel Hilgarth

Mevcut app.config dosyanıza Bölümler ekleyebilirsiniz. Bunu denedim - benim için çalışıyor
Stecya

Sorumdan alıntı: "Not: Varsayılan app.config'in üzerine yazmak istemiyorum!"
Daniel Hilgarth

5
Sorun nedir? Basit: Kullanıcının üzerine yazma hakkı yoktur, çünkü program% ProgramFiles% içine kurulur ve kullanıcı yönetici değildir.
Daniel Hilgarth

2
@Stecya: Çaban için teşekkürler. Ancak sorunun gerçek çözümü için lütfen cevabıma bakın.
Daniel Hilgarth

5

@Daniel çözümü iyi çalışıyor. Daha fazla açıklamaya sahip benzer bir çözüm, keskin köşede. Tamlık için sürümümü paylaşmak istiyorum: ile usingve bit bayrakları kısaltılmış.

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

İlgilenen varsa, işte Mono üzerinde çalışan bir yöntem.

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'in çözümü, daha önce AppDomain.SetData'yı kullandığım aşağı akış montajları için bile işe yarıyor gibi görünüyor, ancak dahili yapılandırma bayraklarını nasıl sıfırlayacağımı bilmiyordu.

İlgilenenler için C ++ / CLI'ye dönüştürüldü

/// <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

Yapılandırma dosyanız "appSettings" içinde anahtar / değerlerle yazıldıysa, bu tür bir kodla başka bir dosya okuyabilirsiniz:

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

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

Ardından, keyValueConfigurationElement koleksiyonu olarak section.Settings'i okuyabilirsiniz.


1
Daha önce de söylediğim gibi, ConfigurationManager.GetSectionoluşturduğum yeni dosyayı okutmak istiyorum . Sizin çözümünüz bunu yapmaz.
Daniel Hilgarth

@Daniel: Neden? "ConfigFilePath" içinde herhangi bir dosyayı belirtebilirsiniz. Bu nedenle, yeni oluşturduğunuz dosyanın konumunu bilmeniz yeterlidir. Bir şey mi kaçırdım ? Ya da gerçekten "ConfigurationManager.GetSection" kullanmanız ve başka bir şey yapmanız gerekmez mi?
Ron

1
Evet, bir şeyi kaçırıyorsunuz: ConfigurationManager.GetSectionvarsayılan app.config'i kullanır. Açtığınız yapılandırma dosyası umurunda değil OpenMappedExeConfiguration.
Daniel Hilgarth

1

Harika tartışma, yöntemdeki ifadenin / çağrıların arkasındaki sihri anlamak için ResetConfigMechanism yöntemine daha fazla yorum ekledim. Ayrıca dosya yolu var kontrol edin

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

Daniel, mümkünse diğer yapılandırma mekanizmalarını kullanmayı dene. Ortama / profile / gruba bağlı olarak farklı statik / dinamik yapılandırma dosyalarına sahip olduğumuz bu rotadan geçtik ve sonunda oldukça dağınık bir hal aldı.

İstemciden yalnızca bir Web Hizmeti URL'si belirttiğiniz ve Müşterinin ayrıntılarına bağlı olarak (Grup / Kullanıcı düzeyi geçersiz kılmalarınız olabilir), ihtiyaç duyduğu tüm yapılandırmayı yükleyen bir tür Profil Web Hizmeti deneyebilirsiniz. Bunun bir kısmı için MS Enterprise Library'yi de kullandık.

istemcinizle yapılandırmayı dağıtmadınız ve bunu müşterilerinizden ayrı olarak yönetebilirsiniz


3
Cevabınız için teşekkürler. Ancak, bunun tüm nedeni yapılandırma dosyalarının gönderilmesinden kaçınmaktır. Modüllerin yapılandırma ayrıntıları bir veritabanından yüklenir. Ancak, modül geliştiricilerine varsayılan .NET yapılandırma mekanizmasının rahatlığını vermek istediğim için, bu modül yapılandırmalarını çalışma zamanında tek bir yapılandırma dosyasına dahil etmek ve bunu varsayılan yapılandırma dosyası yapmak istiyorum. Nedeni basit: app.config aracılığıyla yapılandırılabilen birçok kitaplık vardır (örn. WCF, EntLib, EF, ...). Başka bir yapılandırma mekanizması tanıtacak olsaydım, yapılandırma şöyle olurdu (devam)
Daniel Hilgarth
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.