डिफ़ॉल्ट एप्लिकेशन को बदलें। रनटाइम के दौरान


130

मुझे निम्नलिखित समस्या है:
हमारे पास एक एप्लिकेशन है जो मॉड्यूल लोड करता है (ऑन जोड़ता है)। इन मॉड्यूल को app.config (जैसे WCF कॉन्फ़िगरेशन) में प्रविष्टियों की आवश्यकता हो सकती है। क्योंकि मॉड्यूल गतिशील रूप से लोड किए गए हैं, मैं इन प्रविष्टियों को अपने आवेदन की app.config फ़ाइल में नहीं रखना चाहता।
मैं क्या करना चाहूंगा निम्नलिखित है:

  • एक नया ऐप बनाएं। मेमोरी में जो मॉड्यूल्स से कॉन्फिगर सेक्शन को शामिल करता है
  • उस नए app.config का उपयोग करने के लिए मेरे आवेदन को बताएं

नोट: मैं डिफ़ॉल्ट ऐप को अधिलेखित नहीं करना चाहता हूं ।config!

इसे पारदर्शी तरीके से काम करना चाहिए, ताकि उदाहरण के ConfigurationManager.AppSettingsलिए उस नई फ़ाइल का उपयोग किया जा सके।

इस समस्या के अपने मूल्यांकन के दौरान, मैं उसी समाधान के साथ आया, जो यहां प्रदान किया गया है: ननिट के साथ पुनः लोड करें app ।config
दुर्भाग्य से, यह कुछ भी करने के लिए प्रतीत नहीं होता है, क्योंकि मुझे अभी भी सामान्य 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);
}

यह समान मूल्यों को जुड़वाता है, हालाँकि combinedConfigइसमें सामान्य एप्लिकेशन के अलावा अन्य मूल्य शामिल हैं।


AppDomainउपयुक्त कॉन्फ़िगरेशन फ़ाइल के साथ मॉड्यूल को अलग से होस्ट करना एक विकल्प नहीं है?
जोआ एंजेलो

वास्तव में नहीं, क्योंकि इसके परिणामस्वरूप बहुत सारी क्रॉस-एपडोमेन कॉलें होंगी, क्योंकि एप्लिकेशन मॉड्यूल के साथ काफी भारी बातचीत करता है।
डैनियल हिलगार्थ

जब किसी नए मॉड्यूल को लोड करने की आवश्यकता होती है, तो एप्लिकेशन पुनरारंभ के बारे में कैसे?
जोआ एंजेलो

यह व्यावसायिक आवश्यकताओं के साथ मिलकर काम नहीं करता है। इसके अलावा, मैं app.config को अधिलेखित नहीं कर सकता, क्योंकि उपयोगकर्ता के पास ऐसा करने का अधिकार नहीं है।
डैनियल हिल्गारथ

आप एक अलग App.config को लोड करने के लिए पुनः लोड कर रहे होंगे, प्रोग्राम फ़ाइलों में एक नहीं। Reload app.config with nunitयदि कोई कॉन्फ़िगरेशन लोड होने से पहले अनुप्रयोग प्रविष्टि पर उपयोग किया जाता है, तो हैक काम नहीं कर सकता।
जोआ एंजेलो

जवाबों:


280

लिंक्ड क्वेश्चन में हैक तब काम करता है जब इसे कॉन्फ़िगरेशन सिस्टम के पहले इस्तेमाल किया जाता है। उसके बाद, यह किसी भी अधिक काम नहीं करता है।
कारण: पथ
मौजूद है जो एक वर्ग मौजूद है 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)लिए उपयोग किए गए ऐप को बदलना चाहते हैं, तो बस अपने एप्लिकेशन की शुरुआत में कहीं का उपयोग किए बिना डालें ।


4
यह वास्तव में, वास्तव में उत्कृष्ट है। इसे पोस्ट करने के लिए बहुत - बहुत धन्यवाद।
user981225

3
@ डैनियल यह कमाल था - मैंने इसे ApplicationSettingsBase के लिए एक एक्सटेंशन विधि में काम किया, ताकि मैं Settings.Default.RedirectAppConfig (पाथ) को कॉल कर सकूं। अगर मैं कर सकता तो मैं आपको +2 देता!
JMarsch

2
@PhilWhittington: यही मैं कह रहा हूँ, हाँ।
डैनियल हिल्गारथ

2
रुचि से बाहर, क्या फाइनल को दबाने का कोई कारण है क्या कोई फाइनल घोषित नहीं है?
गुस्सोर

3
एक तरफ, निजी क्षेत्रों तक पहुंचने के लिए प्रतिबिंब का उपयोग करना अब काम कर सकता है, लेकिन यह एक चेतावनी का उपयोग कर सकता है जो समर्थित नहीं है और .NET फ्रेमवर्क के भविष्य के संस्करणों में टूट सकता है।

10

आप कॉन्फ़िगरेशन का उपयोग करने और रनटाइम पर कॉन्फ़िगरेशन जोड़ने का प्रयास कर सकते हैं

Configuration applicationConfiguration = ConfigurationManager.OpenMappedExeConfiguration(
                        new ExeConfigurationFileMap(){ExeConfigFilename = path_to_your_config,
                        ConfigurationUserLevel.None
                        );

applicationConfiguration.Sections.Add("section",new YourSection())
applicationConfiguration.Save(ConfigurationSaveMode.Full,true);

संपादित करें: यहाँ समाधान प्रतिबिंब पर आधारित है (हालांकि बहुत अच्छा नहीं है)

से व्युत्पन्न वर्ग बनाएँ 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

मैं नहीं देखता कि यह कैसे मेरी मदद करता है। यह निर्दिष्ट फ़ाइल में एक अनुभाग जोड़ देगा file_path। यह उपयोगकर्ता के लिए उपलब्ध अनुभाग को उपलब्ध नहीं कराएगा ConfigurationManager.GetSection, क्योंकि GetSectionडिफ़ॉल्ट एप्लिकेशन का उपयोग करता है ।config।
डैनियल हिल्गारथ

आप अपने मौजूदा app.config में अनुभाग जोड़ सकते हैं। बस यह कोशिश की - मेरे लिए काम करता है
स्टेक्या

मेरे प्रश्न से उद्धरण: "ध्यान दें: मैं डिफ़ॉल्ट ऐप को ओवरराइट नहीं करना चाहता हूं ।config!"
डैनियल हिल्गर्थ

5
क्या गलत है? सरल: उपयोगकर्ता के पास इसे अधिलेखित करने का कोई अधिकार नहीं है, क्योंकि प्रोग्राम% ProgramFiles% में स्थापित है और उपयोगकर्ता कोई व्यवस्थापक नहीं है।
डैनियल हिल्गारथ

2
@Stecya: आपके प्रयास के लिए धन्यवाद। लेकिन कृपया समस्या के वास्तविक समाधान के लिए मेरा जवाब देखें।
डैनियल हिलगार्थ

5

@ डेनियल समाधान ठीक काम करता है। अधिक स्पष्टीकरण वाला एक समान समाधान सी-शार्प कॉर्नर में है। पूर्णता के लिए मैं अपना संस्करण साझा करना चाहता / चाहती हूं: 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

अगर किसी को दिलचस्पी है, तो यहां एक तरीका है जो मोनो पर काम करता है।

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);

3

डैनियल का समाधान नीचे की विधानसभाओं के लिए भी काम करता है जो मैंने पहले 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;
}

1

यदि आपकी कॉन्फ़िगर फ़ाइल "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 के संग्रह के रूप में अनुभाग पढ़ सकते हैं।


1
जैसा कि मैंने पहले ही कहा था, मैं अपने द्वारा ConfigurationManager.GetSectionबनाई गई नई फ़ाइल को पढ़ना चाहता हूं। आपका समाधान ऐसा नहीं करता है।
डैनियल हिल्गारथ

@ डैनियल: क्यों? आप किसी भी फ़ाइल को "configFilePath" में निर्दिष्ट कर सकते हैं। तो आपको बस अपनी नई बनाई गई फ़ाइल का स्थान पता होना चाहिए। क्या मैं कुछ भुल गया ? या आपको वास्तव में "configurationManager.GetSection" का उपयोग करने की आवश्यकता है और कुछ नहीं?
रॉन

1
हां आप कुछ मिस करते हैं: ConfigurationManager.GetSectionडिफ़ॉल्ट ऐप का उपयोग करता है ।config। यह आपके द्वारा खोले गए कॉन्फ़िगरेशन फ़ाइल के बारे में परवाह नहीं करता है OpenMappedExeConfiguration
डैनियल हिल्गारथ

1

अद्भुत चर्चा, मैंने विधि में कथन / कॉल के पीछे के जादू को समझने के लिए 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;
}

0

डैनियल, यदि संभव हो तो अन्य कॉन्फ़िगरेशन तंत्र का उपयोग करने का प्रयास करें। हम इस मार्ग से होकर गुजरे हैं जहाँ हमारे पास पर्यावरण / प्रोफाइल / समूह के आधार पर अलग-अलग स्टेटिक / डायनेमिक कॉन्फिग फाइल थीं और यह अंत में काफी गड़बड़ हो गई थी।

आप किसी प्रकार की प्रोफ़ाइल वेब सेवा की कोशिश कर सकते हैं, जहाँ आप केवल ग्राहक से एक वेब सेवा URL निर्दिष्ट करते हैं और ग्राहक के विवरणों पर निर्भर करता है (आपके पास समूह / उपयोगकर्ता स्तर ओवरराइड हो सकता है), यह उन सभी विन्यास को लोड करता है जिनकी इसे ज़रूरत है। हमने इसके कुछ हिस्से के लिए एमएस एंटरप्राइज लाइब्रेरी का भी उपयोग किया है।

आप अपने क्लाइंट के साथ कॉन्फिगर नहीं कर सकते थे और आप इसे अपने क्लाइंट से अलग से मैनेज कर सकते थे


3
आपके उत्तर के लिए धन्यवाद। हालांकि, इसका पूरा कारण शिपिंग कॉन्फिग फाइलों से बचना है। मॉड्यूल के लिए कॉन्फ़िगरेशन विवरण एक डेटाबेस से लोड किए गए हैं। लेकिन क्योंकि मैं मॉड्यूल डेवलपर्स को डिफ़ॉल्ट .NET कॉन्फ़िगरेशन तंत्र का आराम देना चाहता हूं, इसलिए मैं उन मॉड्यूल कॉन्फ़िगरेशन को रनटाइम में एक कॉन्फ़िगरेशन फ़ाइल में शामिल करना चाहता हूं और इसे डिफ़ॉल्ट कॉन्फ़िगरेशन फ़ाइल बनाता हूं। कारण सरल है: बहुत सारे पुस्तकालय मौजूद हैं जिन्हें app.config (जैसे WCF, EntLib, EF, ...) के माध्यम से कॉन्फ़िगर किया जा सकता है। अगर मैं एक और विन्यास तंत्र का परिचय देता, तो विन्यास (
प्रतियोगिता
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.