Thay đổi app.config mặc định trong thời gian chạy


130

Tôi có một vấn đề sau:
Chúng tôi có một ứng dụng tải các mô-đun (tiện ích bổ sung). Các mô-đun này có thể cần các mục trong app.config (ví dụ: cấu hình WCF). Vì các mô-đun được tải động, tôi không muốn có các mục này trong tệp app.config trong ứng dụng của mình.
Những gì tôi muốn làm là như sau:

  • Tạo một app.config mới trong bộ nhớ kết hợp các phần cấu hình từ các mô-đun
  • Nói với ứng dụng của tôi để sử dụng app.config mới đó

Lưu ý: Tôi không muốn ghi đè lên app.config mặc định!

Nó nên hoạt động trong suốt, ví dụ như ConfigurationManager.AppSettingssử dụng tệp mới đó.

Trong quá trình đánh giá vấn đề này, tôi đã đưa ra giải pháp tương tự như được cung cấp ở đây: Tải lại app.config bằng nunit .
Thật không may, nó dường như không làm gì cả, vì tôi vẫn nhận được dữ liệu từ app.config bình thường.

Tôi đã sử dụng mã này để kiểm tra nó:

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

Nó in cùng hai giá trị, mặc dù combinedConfigchứa các giá trị khác với app.config bình thường.


Lưu trữ các mô-đun riêng biệt AppDomainvới tập tin cấu hình phù hợp không phải là một lựa chọn?
João Angelo

Không thực sự, bởi vì điều đó sẽ dẫn đến nhiều cuộc gọi Cross-AppDomain, vì ứng dụng tương tác khá nhiều với các mô-đun.
Daniel Hilgarth

Làm thế nào về một ứng dụng khởi động lại khi một mô-đun mới cần được tải?
João Angelo

Điều này không hoạt động cùng với các yêu cầu kinh doanh. Hơn nữa, tôi không thể ghi đè lên app.config, vì người dùng không có quyền làm như vậy.
Daniel Hilgarth

Bạn sẽ tải lại để tải một App.config khác, không phải ứng dụng trong tệp chương trình. Việc hack in Reload app.config with nunitcó thể hoạt động, không chắc chắn, nếu được sử dụng trên mục nhập ứng dụng trước khi tải bất kỳ cấu hình nào.
João Angelo

Câu trả lời:


280

Việc hack trong câu hỏi được liên kết hoạt động nếu nó được sử dụng trước khi hệ thống cấu hình được sử dụng lần đầu tiên. Sau đó, nó không hoạt động nữa.
Lý do:
Tồn tại một lớp ClientConfigPathslưu trữ các đường dẫn. Vì vậy, ngay cả sau khi thay đổi đường dẫn với SetData, nó không được đọc lại, bởi vì đã tồn tại các giá trị được lưu trữ. Giải pháp là cũng loại bỏ những thứ này:

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

Cách sử dụng là như thế này:

// the default app.config is used.
using(AppConfig.Change(tempFileName))
{
    // the app.config in tempFileName is used
}
// the default app.config is used.

Nếu bạn muốn thay đổi app.config đã sử dụng cho toàn bộ thời gian chạy của ứng dụng, chỉ cần đặt AppConfig.Change(tempFileName)mà không sử dụng ở đâu đó khi bắt đầu ứng dụng của bạn.


4
Điều này thực sự, thực sự xuất sắc. Cảm ơn bạn rất nhiều vì đã gửi bài này.
dùng981225

3
@Daniel Điều đó thật tuyệt vời - Tôi đã áp dụng nó thành một phương thức mở rộng cho ApplicationSinstallBase, để tôi có thể gọi Settings.Default.RedirectAppConfig (đường dẫn). Tôi sẽ cho bạn +2 nếu tôi có thể!
JMarsch

2
@PhilWhittington: Đó là những gì tôi đang nói, vâng.
Daniel Hilgarth

2
Không quan tâm, có bất kỳ lý do để ngăn chặn quyết toán là không có tuyên bố cuối cùng?
Gusdor

3
Ngoài ra, sử dụng sự phản chiếu để truy cập các trường riêng có thể hoạt động ngay bây giờ, nhưng nó có thể sử dụng một cảnh báo rằng nó không được hỗ trợ và có thể phá vỡ trong các phiên bản tương lai của .NET Framework.

10

Bạn có thể thử sử dụng Cấu hình và Thêm Cấu hình trong thời gian chạy

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: Đây là giải pháp dựa trên sự phản chiếu (mặc dù không đẹp lắm)

Tạo lớp bắt nguồn từ 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
}

sau đó thông qua sự phản chiếu đặt nó vào trường riêng trong 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

Tôi không thấy điều này giúp tôi như thế nào. Điều này sẽ thêm một phần vào tập tin được chỉ định bởi file_path. Điều này sẽ không làm cho phần có sẵn cho người dùng ConfigurationManager.GetSection, bởi vì GetSectionsử dụng app.config mặc định.
Daniel Hilgarth

Bạn có thể thêm Phần vào app.config hiện tại của bạn. Chỉ cần thử điều này - làm việc cho tôi
Stecya

Trích dẫn từ câu hỏi của tôi: "Lưu ý: Tôi không muốn ghi đè lên app.config mặc định!"
Daniel Hilgarth

5
Chuyện gì vậy? Đơn giản: Người dùng không có quyền ghi đè lên nó, bởi vì chương trình được cài đặt trong% ProgramFiles% và người dùng không phải là quản trị viên.
Daniel Hilgarth

2
@Stecya: Cảm ơn nỗ lực của bạn. Nhưng xin vui lòng xem câu trả lời của tôi cho giải pháp thực sự cho vấn đề.
Daniel Hilgarth

5

Giải pháp @Daniel hoạt động tốt. Một giải pháp tương tự với nhiều lời giải thích hơn là ở góc c-sharp. Để hoàn thiện, tôi muốn chia sẻ phiên bản của mình: với usingvà các cờ bit được viết tắt.

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

Nếu có ai quan tâm, đây là một phương pháp hoạt động trên 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);

3

Giải pháp của Daniel dường như hoạt động ngay cả đối với các hội đồng xuôi dòng mà tôi đã sử dụng AppDomain.SetData trước đây, nhưng không biết làm thế nào để thiết lập lại các cờ cấu hình bên trong

Chuyển đổi sang C ++ / CLI cho những ai quan tâm

/// <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

Nếu tệp cấu hình của bạn chỉ được ghi bằng khóa / giá trị trong "cài đặt ứng dụng", thì bạn có thể đọc một tệp khác có mã như vậy:

System.Configuration.ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap();
configFileMap.ExeConfigFilename = configFilePath;

System.Configuration.Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None);
AppSettingsSection section = (AppSettingsSection)configuration.GetSection("appSettings");

Sau đó, bạn có thể đọc phần. Cài đặt dưới dạng bộ sưu tập KeyValueConfigurationEuity.


1
Như tôi đã nói, tôi muốn ConfigurationManager.GetSectionđọc tệp mới mà tôi đã tạo. Giải pháp của bạn không làm điều đó.
Daniel Hilgarth

@Daniel: tại sao? Bạn có thể chỉ định bất kỳ tệp nào trong "configFilePath". Vì vậy, bạn chỉ cần biết vị trí của tập tin mới được tạo của bạn. Tôi đã bỏ lỡ một cái gì đó? Hoặc bạn thực sự cần phải sử dụng "Cấu hình Trình quản lý.GetSection" và không có gì khác?
Ron

1
Có bạn bỏ lỡ điều gì đó: ConfigurationManager.GetSectionsử dụng app.config mặc định. Nó không quan tâm đến tập tin cấu hình mà bạn đã mở OpenMappedExeConfiguration.
Daniel Hilgarth

1

Thảo luận tuyệt vời, tôi đã thêm nhiều bình luận vào phương thức ResetConfigMechanism để hiểu được điều kỳ diệu đằng sau câu lệnh / cuộc gọi trong phương thức. Ngoài ra kiểm tra đường dẫn tập tin tồn tại

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

Daniel, nếu có thể hãy thử sử dụng các cơ chế cấu hình khác. Chúng tôi đã đi qua tuyến đường này nơi chúng tôi có các tệp cấu hình tĩnh / động khác nhau tùy thuộc vào môi trường / hồ sơ / nhóm và cuối cùng nó trở nên khá lộn xộn.

bạn có thể thử một số loại Dịch vụ Web hồ sơ trong đó bạn chỉ chỉ định một URL Dịch vụ web từ máy khách và tùy thuộc vào chi tiết của Khách hàng (bạn có thể ghi đè cấp Nhóm / Người dùng), nó tải lên tất cả cấu hình cần thiết. Chúng tôi cũng đã sử dụng Thư viện MS Enterprise cho một số phần của nó.

đó là bạn không triển khai cấu hình với máy khách của mình và bạn có thể quản lý nó riêng biệt với máy khách của mình


3
Cảm ơn câu trả lời của bạn. Tuy nhiên, toàn bộ lý do của việc này là để tránh vận chuyển tập tin cấu hình. Các chi tiết cấu hình cho các mô-đun được tải từ cơ sở dữ liệu. Nhưng vì tôi muốn mang đến cho các nhà phát triển mô-đun sự thoải mái của cơ chế cấu hình .NET mặc định, tôi muốn kết hợp các cấu hình mô-đun đó vào một tệp cấu hình trong thời gian chạy và biến đây thành tệp cấu hình mặc định. Lý do rất đơn giản: Tồn tại rất nhiều thư viện có thể được cấu hình thông qua app.config (ví dụ: WCF, EntLib, EF, ...). Nếu tôi giới thiệu một cơ chế cấu hình khác, cấu hình sẽ (tiếp)
Daniel Hilgarth
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.