Изменить приложение по умолчанию.конфигурации во время выполнения
у меня следующая проблема:
У нас есть приложение, которое загружает модули (дополнений). Эти модули могут нуждаться в записях в приложении.конфигурация (например, конфигурация WCF). Потому что модули загружаются динамически, я не хочу иметь эти записи в приложении.конфигурационный файл моего приложения.
То, что я хотел бы сделать, это следующее:
- создать новое приложение.конфигурация в памяти, которая включает в себя разделы конфигурации из модулей
- рассказать мой приложение для использования этого нового приложения.конфигурации
примечание: Я не хочу, чтобы перезаписать приложение по умолчанию.конфиг!
Он должен работать прозрачно, так что, например,ConfigurationManager.AppSettings
использует новый файл.
во время моей оценки этой проблемы я придумал то же решение, что и здесь: перезагрузить приложение.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);
}
он печатает те же значения дважды, хотя combinedConfig
содержит другие значения, чем обычное приложение.конфиг.
8 ответов:
Хак в связанном вопросе работает, если он используется до того, как система конфигурации используется в первый раз. После этого он больше не работает.
Причина:
Существует класс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.
если вы хотите изменить используемый приложение.конфигурация для всего времени выполнения вашего приложения, проще говоря
AppConfig.Change(tempFileName)
без использования где-то в начале вашего приложения.
вы можете попробовать использовать конфигурация и добавить 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
@Daniel решение работает нормально. Аналогичное решение с большим объяснением находится в С-острый угол. Для полноты картины я хотел бы поделиться своей версией: с
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; }
решение Даниэля, похоже, работает даже для нижестоящих сборок Я использовал AppDomain.SetData раньше, но не знал, как сбросить внутренние флаги конфигурации
преобразуется в C++ / 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; }
Если кто-то заинтересован, вот метод, который работает на моно.
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);
Если ваш конфигурационный файл просто записан с ключом / значениями в "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");
тогда вы можете прочитать раздел.Настройки как коллекция KeyValueConfigurationElement.
замечательная дискуссия, я добавляю больше комментариев к методу ResetConfigMechanism, чтобы понять магию, стоящую за оператором/вызовами в методе. Также добавлен путь к файлу exist check
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; }
Даниэль, если возможно, попробуйте использовать другие механизмы конфигурации. Мы прошли через этот маршрут, где у нас были разные статические/динамические файлы конфигурации в зависимости от среды/профиля/группы, и в конце концов это стало довольно грязным.
вы можете попробовать какой-то профиль WebService, где вы указываете только один URL-адрес веб-службы от клиента и в зависимости от деталей клиента (у вас могут быть переопределения уровня группы/пользователя), он загружает всю необходимую конфигурацию. Мы также использовали MS Корпоративная библиотека для некоторой ее части.
Это было вы не развертываете конфигурацию с вашим клиентом, и вы можете управлять им отдельно от ваших клиентов