在运行时更改默认app.config

Dan*_*rth 128 .net c# app-config

我有以下问题:
我们有一个加载模块的应用程序(添加).这些模块可能需要app.config中的条目(例如WCF配置).因为模块是动态加载的,所以我不希望在我的应用程序的app.config文件中包含这些条目.
我想做的是以下内容:

  • 在内存中创建一个新的app.config,它包含模块中的配置部分
  • 告诉我的应用程序使用新的app.config

注意:我不想覆盖默认的app.config!

它应该透明地工作,以便例如ConfigurationManager.AppSettings使用该新文件.

在我这个问题的评价,我用同样的解决方案提出了为这里提供:刷新的app.config与NUnit的.
不幸的是,它似乎没有做任何事情,因为我仍然从正常的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);
}
Run Code Online (Sandbox Code Playgroud)

它打印相同的值twices,虽然combinedConfig包含除正常app.config之外的其他值.

Dan*_*rth 272

如果在第一次使用配置系统之前使用链接问题,那么黑客就会工作.在那之后,它不再起作用了.
原因:
存在一个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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法是这样的:

// the default app.config is used.
using(AppConfig.Change(tempFileName))
{
    // the app.config in tempFileName is used
}
// the default app.config is used.
Run Code Online (Sandbox Code Playgroud)

如果要更改应用程序的整个运行时使用的app.config,只需AppConfig.Change(tempFileName)在应用程序启动时不使用某个地方即可.

  • 这真的非常非常好.非常感谢您发布此内容. (3认同)
  • 除此之外,使用反射来访问私有字段现在可以正常工作,但它可能会使用一个不受支持的警告,并且可能在.NET Framework的未来版本中中断. (3认同)
  • @Daniel那太棒了 - 我把它用于ApplicationSettingsBase的exentension方法,这样我就可以调用Settings.Default.RedirectAppConfig(path)了.如果可以,我会给你+2! (2认同)
  • @PhilWhittington:这就是我说的,是的. (2认同)
  • 出于兴趣,是否有任何理由压制终结者是否没有宣告终结者? (2认同)
  • @DanielHilgarth 实现 Dispose 调用 `GC.SuppressFinalize` 的标准模式涉及一个 `Dispose()` 非虚方法和一个 `Dispose(bool)` 虚方法。您实际上并未使用标准的 Dispose 模式。无论如何,即使基类没有终结器,`GC.SuppressFinalize` 的重点是因为派生类可能,但除非你真的想支持从`AppConfig` 派生的其他类,它没有任何好处。事实上,我会精确地给`AppConfig` 一个私有构造函数,以便其他类*不能*使用它作为基础。 (2认同)
  • `ResetConfigMechanism` 中的所有 `typeof(ConfigurationManager).GetField` 都返回 null... 有什么问题? (2认同)
  • 关于我之前的评论:添加 `using System.Configuration` 使重置机制正常工作。不幸的是,新的 app.config 没有被使用,应用程序仍然是指原始的。 (2认同)
  • 我之前评论的解决方案是创建一个静态类,该类允许在实例化需要修改的 app.config 的类之前调用​​`AppConfig.Change(tempFileName)`。 (2认同)

Ste*_*cya 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);
Run Code Online (Sandbox Code Playgroud)

编辑:这是基于反射的解决方案(虽然不是很好)

创建派生自的类 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
}
Run Code Online (Sandbox Code Playgroud)

然后通过反射将其设置为私有字段 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
Run Code Online (Sandbox Code Playgroud)

  • 怎么了?简单:用户无权覆盖它,因为程序安装在%ProgramFiles%中且用户不是管理员. (5认同)
  • @Stecya:谢谢你的努力.但请查看我的答案,找出问题的真正解决方案. (2认同)

Lio*_*hAu 6

如果有人感兴趣,这里有一个适用于 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);
Run Code Online (Sandbox Code Playgroud)


Rol*_*and 5

@Daniel 解决方案工作正常。具有更多解释的类似解决方案位于c-sharp 角。 为了完整起见,我想分享我的版本: withusing和缩写的位标志。

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;
    }
Run Code Online (Sandbox Code Playgroud)