我喜欢在运行时更新AppSettings
Web.config
部分中定义的键/值。但是我不想将它们实际保存到Web.config
文件中。
我有一个巨大的Web应用程序,它包含许多模块,DLL和源代码文件。从Web服务的数据库配置,加密密钥,用户名和密码等一系列关键信息保存在AppSettings
文件的web.config
部分。最近的项目要求需要我将这些值移出web.config
并保存在安全存储中。
我已经在外部位置保护了这些值,我可以在应用程序启动时将它们读回来。
这是示例代码。
Global.asax中
public class Global: System.Web.HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
Dictionary<string, string> secureConfig = new Dictionary<string,string>{};
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey","VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
ConfigurationManager.AppSettings.Add(item.Key, item.Value);
}
}
}
您可能已经注意到,在由多个编程团队创建的大量代码中更改对AppSettings
的引用以从我的secureConfig dictionary
读取其设置是不可行的,另一方面我不应该保存这些web.config
文件中的值,可供Web管理员和操作员,系统管理员和云管理员使用。
为了让程序员的生活更轻松,我想让他们在开发期间将他们的值添加到AppSettings
web.config
部分,但是他们将从那里删除并在部署期间安全存储,但是这些值应该可以透明地编程,因为它们仍然在AppSettings
部分。
问题:如何在运行时向AppSettings
添加值,以便程序可以使用ConfigurationManager.AppSettings["ACriticalKey"]
读取它们以获取"VeryCriticalValue"
而不将其保存在Web.Config中?
请注意:ConfigurationManager.AppSettings.Add(item.Key, item.Value);
向我ConfigurationErrorsException
发送消息The configuration is read only.
请注意:最好是某些设置应该像以前一样留在AppSettings
答案 0 :(得分:9)
我知道这是一个老问题,但我遇到了同样的问题,我发现Set的工作方式与Add相同,并且不会抛出异常,所以只需将Add替换为Set,就像这样:
ConfigurationManager.AppSettings.Set(item.Key, item.Value);
答案 1 :(得分:4)
您需要使用WebConfigurationManager.OpenWebConfiguration()
Configuration config = WebConfigurationManager.OpenWebConfiguration(HttpContext.Current.Request.ApplicationPath);
config.AppSettings.Settings.Remove("Variable");
config.AppSettings.Settings.Add("Variable", "valyue");
config.Save();
答案 2 :(得分:1)
也许this链接会有所帮助。它引用了2.0,但我相信该方法在4.0中仍然有效。
此外,关于相同/类似主题here的SO问题可能会引起关注。
此外,在运行时修改web.config应该每次都会导致应用程序池回收。不要试图告诉你如何吮吸鸡蛋,只是想我会注意到任何人的未来兴趣... Thx。
答案 3 :(得分:0)
感谢nkvu指导我找到了他的第一个链接,然后又将我发送到Williarob的帖子“Override Configuration Manager”我找到了解决问题的方法。
上述博客文章介绍了如何从其他XML文件中读取设置,并且它适用于窗口化应用程序和Web应用程序(在配置文件名和路径中稍作修改)。尽管这篇博客是在2010年写的,但它仍然可以正常使用.NET4而没有问题。
然而,当我要从安全设备读取配置时,我简化了课程,以下是如何使用Williarob提供的课程
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Configuration.Internal;
using System.Linq;
using System.Reflection;
namespace Williablog.Core.Configuration {
public sealed class ConfigSystem: IInternalConfigSystem {
private static IInternalConfigSystem clientConfigSystem;
private object appsettings;
private object connectionStrings;
/// <summary>
/// Re-initializes the ConfigurationManager, allowing us to merge in the settings from Core.Config
/// </summary>
public static void Install() {
FieldInfo[] fiStateValues = null;
Type tInitState = typeof(System.Configuration.ConfigurationManager).GetNestedType("InitState", BindingFlags.NonPublic);
if (null != tInitState) {
fiStateValues = tInitState.GetFields();
}
FieldInfo fiInit = typeof(System.Configuration.ConfigurationManager).GetField("s_initState", BindingFlags.NonPublic | BindingFlags.Static);
FieldInfo fiSystem = typeof(System.Configuration.ConfigurationManager).GetField("s_configSystem", BindingFlags.NonPublic | BindingFlags.Static);
if (fiInit != null && fiSystem != null && null != fiStateValues) {
fiInit.SetValue(null, fiStateValues[1].GetValue(null));
fiSystem.SetValue(null, null);
}
ConfigSystem confSys = new ConfigSystem();
Type configFactoryType = Type.GetType("System.Configuration.Internal.InternalConfigSettingsFactory, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
IInternalConfigSettingsFactory configSettingsFactory = (IInternalConfigSettingsFactory) Activator.CreateInstance(configFactoryType, true);
configSettingsFactory.SetConfigurationSystem(confSys, false);
Type clientConfigSystemType = Type.GetType("System.Configuration.ClientConfigurationSystem, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
clientConfigSystem = (IInternalConfigSystem) Activator.CreateInstance(clientConfigSystemType, true);
}
#region IInternalConfigSystem Members
public object GetSection(string configKey) {
// get the section from the default location (web.config or app.config)
object section = clientConfigSystem.GetSection(configKey);
switch (configKey) {
case "appSettings":
// Return cached version if exists
if (this.appsettings != null) {
return this.appsettings;
}
// create a new collection because the underlying collection is read-only
var cfg = new NameValueCollection();
// If an AppSettings section exists in Web.config, read and add values from it
if (section is NameValueCollection) {
NameValueCollection localSettings = (NameValueCollection) section;
foreach (string key in localSettings) {
cfg.Add(key, localSettings[key]);
}
}
// --------------------------------------------------------------------
// Here I read and decrypt keys and add them to secureConfig dictionary
// To test assume the following line is a key stored in secure sotrage.
//secureConfig = SecureConfig.LoadConfig();
secureConfig.Add("ACriticalKey", "VeryCriticalValue");
// --------------------------------------------------------------------
foreach (KeyValuePair<string, string> item in secureConfig) {
if (cfg.AllKeys.Contains(item.Key)) {
cfg[item.Key] = item.Value;
} else {
cfg.Add(item.Key, item.Value);
}
}
// --------------------------------------------------------------------
// Cach the settings for future use
this.appsettings = cfg;
// return the merged version of the items from secure storage and appsettings
section = this.appsettings;
break;
case "connectionStrings":
// Return cached version if exists
if (this.connectionStrings != null) {
return this.connectionStrings;
}
// create a new collection because the underlying collection is read-only
ConnectionStringsSection connectionStringsSection = new ConnectionStringsSection();
// copy the existing connection strings into the new collection
foreach (ConnectionStringSettings connectionStringSetting in ((ConnectionStringsSection) section).ConnectionStrings) {
connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
}
// --------------------------------------------------------------------
// Again Load connection strings from secure storage and merge like below
// connectionStringsSection.ConnectionStrings.Add(connectionStringSetting);
// --------------------------------------------------------------------
// Cach the settings for future use
this.connectionStrings = connectionStringsSection;
// return the merged version of the items from secure storage and appsettings
section = this.connectionStrings;
break;
}
return section;
}
public void RefreshConfig(string sectionName) {
if (sectionName == "appSettings") {
this.appsettings = null;
}
if (sectionName == "connectionStrings") {
this.connectionStrings = null;
}
clientConfigSystem.RefreshConfig(sectionName);
}
public bool SupportsUserConfig { get { return clientConfigSystem.SupportsUserConfig; } }
#endregion
}
}
要安装此(或配置覆盖的原始版本),请添加以下行 你的全球。 Application_Start中的class(Global.asax.cs)
Williablog.Core.Configuration.ConfigSystem .Install();
如下所示:
public class Global: System.Web.HttpApplication {
//...
#region protected void Application_Start(...)
protected void Application_Start(object sender, EventArgs e) {
Williablog.Core.Configuration.ConfigSystem .Install();
//...
}
#endregion
//...
}