Mam następujący problem:
Mamy aplikację, która ładuje moduły (dodatki). Te moduły mogą wymagać wpisów w pliku app.config (np. Konfiguracji WCF). Ponieważ moduły są ładowane dynamicznie, nie chcę, aby te wpisy były w pliku app.config mojej aplikacji.
Chciałbym wykonać następujące czynności:
- Utwórz nowy plik app.config w pamięci, który zawiera sekcje konfiguracyjne z modułów
- Powiedz mojej aplikacji, aby używała tego nowego pliku app.config
Uwaga: nie chcę nadpisywać domyślnego pliku app.config!
Powinien działać w sposób przejrzysty, aby na przykład ConfigurationManager.AppSettings
używał tego nowego pliku.
Podczas oceny tego problemu wpadłem na to samo rozwiązanie, które podano tutaj: Załaduj ponownie plik app.config za pomocą nunit .
Niestety wydaje się, że nic nie daje, ponieważ nadal otrzymuję dane z normalnego pliku app.config.
Użyłem tego kodu, aby to przetestować:
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);
}
Drukuje te same wartości dwa razy, chociaż combinedConfig
zawiera inne wartości niż zwykły plik app.config.
źródło
AppDomain
z odpowiednim plikiem konfiguracyjnym nie wchodzi w grę?Reload app.config with nunit
może zadziałać, nie jestem pewien, jeśli zostanie użyte przy wejściu do aplikacji przed załadowaniem jakiejkolwiek konfiguracji.Odpowiedzi:
Hack w powiązanym pytaniu działa, jeśli zostanie użyty przed pierwszym użyciem systemu konfiguracyjnego. Potem już nie działa.
Powód:
istnieje klasa,
ClientConfigPaths
która buforuje ścieżki. Tak więc nawet po zmianie ścieżki za pomocąSetData
nie jest ona ponownie odczytywana, ponieważ istnieją już wartości w pamięci podręcznej. Rozwiązaniem jest usunięcie również tych: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); } } }
Użycie jest takie:
// the default app.config is used. using(AppConfig.Change(tempFileName)) { // the app.config in tempFileName is used } // the default app.config is used.
Jeśli chcesz zmienić używany plik app.config na cały czas wykonywania swojej aplikacji, po prostu umieść go
AppConfig.Change(tempFileName)
bez użycia gdzieś na początku aplikacji.źródło
Możesz spróbować użyć Configuration i Add ConfigurationSection w czasie wykonywania
Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration( new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config, ConfigurationUserLevel.None ); applicationConfiguration.Sections.Add("section",new YourSection()) applicationConfiguration.Save(ConfigurationSaveMode.Full,true);
EDYCJA: Oto rozwiązanie oparte na refleksji (choć niezbyt ładne)
Utwórz klasę pochodną z
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 }
następnie przez odbicie ustaw na pole prywatne w
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
źródło
file_path
. Nie spowoduje to udostępnienia sekcji użytkownikom programuConfigurationManager.GetSection
, ponieważGetSection
używa domyślnego pliku app.config.Rozwiązanie @Daniel działa OK. Podobne rozwiązanie z większym wyjaśnieniem znajduje się w ostrym narożniku. Dla kompletności chciałbym udostępnić moją wersję: z
using
, a flagi bitowe zostały skrócone.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; }
źródło
Jeśli ktoś jest zainteresowany, oto metoda, która działa na 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);
źródło
Rozwiązanie Daniela wydaje się działać nawet w przypadku zestawów podrzędnych, z których korzystałem wcześniej AppDomain.SetData, ale nie wiedziałem, jak zresetować wewnętrzne flagi konfiguracji
Przekonwertowany na C ++ / CLI dla zainteresowanych
/// <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; }
źródło
Jeśli plik konfiguracyjny jest właśnie zapisany z kluczami / wartościami w „appSettings”, możesz odczytać inny plik z takim kodem:
System.Configuration.ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap(); configFileMap.ExeConfigFilename = configFilePath; System.Configuration.Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); AppSettingsSection section = (AppSettingsSection)configuration.GetSection("appSettings");
Następnie możesz przeczytać sekcję.Settings jako kolekcja KeyValueConfigurationElement.
źródło
ConfigurationManager.GetSection
odczytał nowy plik, który utworzyłem. Twoje rozwiązanie tego nie robi.ConfigurationManager.GetSection
używa domyślnego pliku app.config. Nie obchodzi go plik konfiguracyjny, który otworzyłeśOpenMappedExeConfiguration
.Cudowna dyskusja, dodałem więcej komentarzy do metody ResetConfigMechanism, aby zrozumieć magię instrukcji / wywołań w metodzie. Dodano również sprawdzenie istnienia ścieżki pliku
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; }
źródło
Daniel, jeśli to możliwe, spróbuj użyć innych mechanizmów konfiguracyjnych. Przeszliśmy przez tę trasę, w której mieliśmy różne statyczne / dynamiczne pliki konfiguracyjne w zależności od środowiska / profilu / grupy i na końcu stało się dość niechlujne.
możesz wypróbować pewnego rodzaju usługę Profile WebService, w której określasz tylko jeden adres URL usługi sieci Web z klienta iw zależności od szczegółów klienta (możesz mieć nadpisania na poziomie grupy / użytkownika), ładuje całą potrzebną konfigurację. W części wykorzystaliśmy również bibliotekę MS Enterprise.
to znaczy, że nie wdrażasz konfiguracji z klientem i możesz nią zarządzać niezależnie od klientów
źródło