Изменение по умолчанию app.config во время выполнения
У меня есть следующая проблема:
У нас есть приложение, которое загружает модули (add ons). Этим модулям могут потребоваться записи в 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);
}
Он печатает те же самые значения twices, хотя combinedConfig
содержит другие значения, чем обычный app.config.
Ответы
Ответ 1
Взлом связанного вопроса работает, если он используется до того, как система конфигурации будет использоваться в первый раз. После этого он больше не работает.
Причина:
Существует класс 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)
без использования где-нибудь в начале вашего приложения.
Ответ 2
Вы можете попытаться использовать Configuration и добавить 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);
EDIT: Вот решение, основанное на отражении (не очень приятно)
Создать класс, полученный из 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
Ответ 3
@Решение Daniel работает нормально.
Аналогичное решение с большим количеством объяснений находится в c-sharp corner.
Для полноты я хотел бы поделиться своей версией: с 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
Решение Daniel работает даже для последующих сборок
Раньше я использовал AppDomain.SetData, но не знал о том, как reset флаги внутренней конфигурации
Преобразован в С++/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;
}
Ответ 5
Если кому-то интересно, вот метод, который работает на 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);
Ответ 6
Если ваш файл конфигурации просто написан с ключом/значениями в "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.
Ответ 7
Замечательное обсуждение, я добавляю больше комментариев к методу 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;
}
Ответ 8
Даниэль, если возможно, попытайтесь использовать другие механизмы конфигурации. Мы прошли через этот маршрут, где у нас были разные статические/динамические файлы конфигурации в зависимости от среды/профиля/группы, и в конце концов он стал довольно беспорядочным.
вы можете опробовать какой-то профиль WebService, где вы указываете только один URL-адрес веб-службы от клиента и в зависимости от деталей клиента (возможно, у вас есть переопределение уровня Group/User), он загружает всю необходимую ему конфигурацию. Мы также использовали MS Enterprise Library для некоторой части этого.
что вы не развертывали конфигурацию с вашим клиентом, и вы можете управлять им отдельно от своих клиентов.