.Net 4.X 提前用上 .Net Core 的配置模式以及熱過載配置

張彳艮水發表於2018-03-15

1. 前言

在提倡微服務及 Serverless 越來越普及的當下,傳統 .Net 應用的配置模式往往依賴於一個名為 web.config 的 XML 檔案,在可擴充套件性和可讀性與時代脫節了。當然,我不會慫恿一下子把所有應用遷移到 .Net Core 上,本文將在儘量不引入 .Net Core 開發模式的前提下,獲得最大的利益。

在開始之前,我們還是先說說 .Net Core 的配置模式有何優勢以及最少的依賴。

1.1 .Net Core 配置模式的優勢

  • 支援多種格式,如 Json、ini、Yaml、系統環境變數等
  • 不再依賴於 web.config ,可同時使用多種配置格式
  • 支援熱過載配置,修改配置可以不用重啟應用

1.2 最少依賴

nuget install  Microsoft.Extensions.Configuration
nuget install Microsoft.Extensions.Configuration.Binder
nuget install Castle.Windsor (其他 IOC 框架均可)
複製程式碼

如果你安裝的是最新的包,可能會遇到 Microsoft.Extensions.Configuration 系列 Nuget 包無法安裝的問題,這主要取決當前應用的 .Net 版本,請參考下圖,安裝對應的版本(目前不支援 .Net 4.5 以下的應用)。

版本相容情況

由於本人喜歡可讀性高的 Json 檔案,所以還安裝 Microsoft.Extensions.Configuration.Json 的 Nuget 包。

2. 示例教程

本文在 Abp 2.1.3 的基礎上實現 .Net Core 的配置模式以及熱過載配置,更詳細的過程可參照我在 Github 上的 提交歷史

2.1 載入配置

.Net Core 配置模式的核心是一個名為 IConfigurationRoot 的介面物件,需要在應用入口中載入各種配置格式後建立一個 IConfigurationRoot 的例項,在傳統的 .Net Web 應用中是在 Global.asax.cs 中賦值。

// ConfigurationExtenion.AppConfiguration 為一個靜態的 IConfigurationRoot 例項。
 ConfigurationExtenion.AppConfiguration = new ConfigurationBuilder()
                .AddJsonFile("appsetting.json", optional: false, reloadOnChange: true)
                .AddJsonFile("appsetting01.json", optional: true, reloadOnChange: false)
                .Build();
複製程式碼

簡單地解釋一下,我們需要(在根目錄中) 有一個名為 "appsetting.json" 的 Json 檔案,被修改的同時會過載 ConfigurationExtenion.AppConfiguration 。與之相反的 "appsetting01.json" 則允許不存在,即使存在,被修改時不會過載配置。

2.2 讀取配置節點

載入配置後,我們需要把配置讀取出來,在 IConfigurationRoot 中所有配置的資訊都是存在於一個個節點中,我們可以根據節點名稱來獲取對應的型別物件。

        /// <summary>
        /// 獲取節點配置
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="config"></param>
        /// <param name="sectionName"></param>
        /// <returns></returns>
        internal static TService GetSectionObject<TService>(IConfigurationRoot config, String sectionName)
        {
            var section = config.GetSection(sectionName);
            if (section == null)
            {
                throw new ArgumentException($" {sectionName} 未繫結,無法獲取到配置節點資訊!");
            }
            return section.Get<TService>();
        }
複製程式碼

假設我們有一個這樣的 "appsetting.json" 檔案:

{
    "RedisConfiguration": {
      "InstanceDbId": 14,
      "InstanceRedisConnectionString": "127.0.0.1"
    },
    "MongoDbConfiguration": {
      "ConnectionString": "mongodb://127.0.0.1:27017/?connectTimeoutMS=300000",
      "DatatabaseName": "local"
    }
  }
複製程式碼

如果我們要獲取 MongoDbConfiguration 下的 ConnectionString 的值,那麼我們可以這樣獲取:

var connectionString= GetSectionObject<String>(ConfigurationExtenion.AppConfiguration,"MongoDbConfiguration:ConnectionString");
複製程式碼

2.3 設計配置類

在傳統的 .Net 應用程式中,我們往往會使用一個靜態變數去存放配置資訊。而在有 IOC 的情況下,更好的方法是設計一個類來存放配置,如上面的 Json 檔案我們可以設計如下兩個類(在 Visual Studio 選擇性黏貼 Json 會自動生成物件):

    public class RedisConfiguration
    {
        public int InstanceDbId { get; set; }
        public string InstanceRedisConnectionString { get; set; }
    }

    public class MongodbConfiguration
    {
        public string ConnectionString { get; set; }
        public string DatatabaseName { get; set; }
    }
複製程式碼

2.4 註冊配置

為了實現熱過載配置,而不是一層不變的值,我們在 IOC 中獲取配置類時,需要使用工廠方法獲取。在 Windsor 中可以這麼做:

        /// <summary>
        /// 註冊方法
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="ioc"></param>
        /// <param name="factoryMethod"></param>
        private static void Register<TService>(IIocManager ioc, 
                                    Func<TService> factoryMethod) where TService : class
        {
            ioc.IocContainer
                .Register(
                    Component.For<TService>()
                        .UsingFactoryMethod(factoryMethod)
                        .LifestyleTransient() //這裡的生命週期是瞬時的,單例不可以嗎?
                );
        }
複製程式碼

結合前面的獲取配置節點,我們可以把兩個靜態方法組合起來創造一個新的靜態方法,更方便我們使用。

        /// <summary>
        /// 註冊配置
        /// </summary>
        /// <typeparam name="TService"></typeparam>
        /// <param name="ioc"></param>
        /// <param name="config"></param>
        /// <param name="sectionName"></param>
        internal static void InitConfigService<TService>(IIocManager ioc, IConfigurationRoot config, String sectionName) where TService : class
        {
            Register(ioc, () =>
            {
                var service = GetSectionObject<TService>(config, sectionName);
                return service;
            });
        }
複製程式碼

對於前面兩個配置類,我們可以這樣注入:

   ConfigurationExtenion.InitConfigService<RedisConfiguration>(IocManager,ConfigurationExtenion.AppConfiguration, "RedisConfiguration");
   ConfigurationExtenion.InitConfigService<MongodbConfiguration>(IocManager, ConfigurationExtenion.AppConfiguration, "MongoDbConfiguration");
複製程式碼

2.5 獲取配置

我在這裡新增一個控制器方法獲取 RedisConfiguration 物件,該方法使用屬性注入獲取指定的配置類,並返回給頁面。

       public String GetRedisConfig()
        {
            var redisConfig = IocManager.Instance.Resolve<RedisConfiguration>();
            return redisConfig.ToJsonString();
        }
複製程式碼

可以看到頁面顯示的與 Json 檔案中結構一模一:

結果

2.6 熱過載配置

與 .Net Core 不一樣,在 .Net 4.X 的 Web 應用中只是稍微修改下 Json 檔案都會讓整個應用重啟(修改 web.config 同樣會重啟),所以我將 "appsetting.json" 重新命名為 "appsetting.conf" ,在執行時修改某些值,並重新訪問控制器,可以看到對應的值變了。

效果
需要注意 IIS 的安全配置,Json 或者 ini 也許能直接通過 HTTP 獲取!

3. 結語

通過簡單的使用 .Net Core 配置模式,我們可以感受到其強大魅力,如果你對 .Net Core 更多的瞭解,以及感受兩者的對比,可以對照我之前寫過的一篇 文章 。對於熱過載配置,.Net Core 中更多是使使用 IOptionsSnapshot ,而為了儘量少地引入 .Net Core 的特性,在這裡只是簡單地用了 IOC 的特性。當然,在不使用任何 IOC 的情況下(如果你害怕引入 IOC ),定義一個 IConfigurationRoot 的全域性靜態例項,也不失為一個折中的方案。

相關文章