再來一個原本在 ASP.NET 不用但搬到 ASP.NET Core 讓我傻住的簡單需求 - 如何在類別程式庫(Class Library)讀取 ASP.NET Core appSettings.json 裡的設定?

.NET Core 不再使用 exe.config 或 web.config 存放 appSettings,而是改存 appsettings.json。.NET 時代要讀取 appSettings,一律用 System.Configuration.ConfigurationManager 就對了,它會看著辦,若是 Console/WinForm/WPF 從 exe.config 拿;若是 ASP.NET 則到 web.config 取。

.NET Core 不再有 System.Configuration 命名空間,取而代之的是 Microsoft.Extensions.Configuration.IConfiguration 介面,讀取程式碼對映的是介面,不需要了解底層如何實作。依循此一設計理念,正確的做法是讓類別程式庫也透過 ASP.NET Core 的 DI 依賴注入機制取得 IConfiguration,而非自己去讀取 appsettings.json 進行解析,一個很重要的原因是 appSettings.json 並非 ASP.NET Core IConfiguration 唯一的設定來源,而是依序由六個地方取得:參考

  1. ChainedConfigurationProvider 主機通用的設定,例如以 ASPNETCORE_ 起首的環境變數
  2. appsettings.json
  3. appsettings.環境別.json
    例如:appsettings.Production.json、appsettings.Development.json
  4. App Secrets 應用於正式環境,如 Azure Key Value 金鑰保管機制
  5. OS 的環境變數
  6. 命令列參數

那要如何在類別程式存取 IConfiguration?用實例來示範,假設我們有個 .NET Core 類別程式庫專案 - Foo,其中 FooClass 有個 Test() 方法想讀取 另一個 ASP.NET Core 專案 appsettings.json 中的 AllowedHosts 設定。ASP.NET Core 專案參照 Foo 專案,預備在 Controller 中呼叫 FooClass:

第一步要讓 FooClass 認識 IConfiguration,故要參照 Microsoft.Extensions.Configuration NuGet Package:

再來就是依循之前介紹過的 ASP.NET Core DI 設計技巧 - 透過建構式參數取得 IConfiguration:

using Microsoft.Extensions.Configuration;
using System;

namespace Foo
{
    public class FooClass
    {
        private readonly IConfiguration configruration;

        public FooClass(IConfiguration configruration)
        {
            this.configruration = configruration;
        }

        public string TestReadAppSetting()
        {
            return configruration["AllowedHosts"];
        }
    }
}

既然要融合 ASP.NET Core DI,使用 FooClass 時不再是直接 new 一個 Instance,需先在 Startup.cs ConfigureServices() 註冊:

public void ConfigureServices(IServiceCollection services)
{
    //...略...
    services.AddScoped<Foo.FooClass>();
    //...略...
}

註冊後,我們便可以在 Controller、Model、Razor Page Model 經由建構式參數取得 FooClass Instance,而 ASP.NET Core 建立 FooClass 時會傳入網站使用中的 IConfiguration,這樣子所有的東西就串連在一起囉。在 HomeController 加入簡單測試,成功!

【補充】

這篇文章主要著眼於 ASP.NET 移植到 ASP.NET Core,原有程式寫法要對映的替代做法,未涉及設計架構面優化的考量。

從類別程式庫讀取程序(網站或 exe)的設定值是一種可行做法但未必是最好的,雖然簡單直覺但也有缺點。由於類別會用到什麼設定值是隱含的,從呼叫介面上看不出來,必須由說明文件或追程式碼才知,開發者容易遺漏或誤用出錯。一種更嚴謹的做法是明確宣告專屬的 Option 類別,使用端明確指定傳入類別,一來強型別化,二來類別有哪些參數可調一目瞭然。

ASP.NET WebForm 時代我常使用靜態類別,習慣抄起來就用,類別直接讀取 config 感覺很自然;ASP.NET Core 強迫使用 DI 物件全面 Instance 化後,固定有建構式與建構式參數,多一個參數傳入設定反倒理所當然。如此改用 Options 參數取代直接讀取 appSetting 已不再那麼突兀,是值得考慮的方向。

感謝讀者 Phoenix 提醒。

Tips of how to read appSettings of ASP.NET Core web site from class library.


Comments

# by Phoenix

類別庫讀取appSettings感覺不是很好的作法, 利用options pattern,由startup或main函式間接將設定值傳遞進類別庫內, 這樣對於類別庫的共用彈性會有比較靈活的應用

# by Jeffrey

to Phoenix,這篇文章主要著眼於 ASP.NET 移植到 ASP.NET Core,原有程式寫法要對映的替代做法,未涉及設計架構面優化的考量,但這點該提的,已補充於文末。謝謝你的提醒。

# by 小黑

黑大, 感謝此篇的分享,想延伸請教,下一步就是相關的設定檔加密,以前是可使用IIS 加密工具將相關 config 加密,正式機也可使用匯入金鑰方式維持站台正常運作,不知.net core 有無支援相關對應解決方案?

# by 凱大

因為注意到你用的是 3.0up 的版本 其實如果考量你部屬環境有 dotnet core runtime or runtime store 其實可以考慮在非 web project 中或是 Build.Directory.props 中加入 <FrameworkReference Include="Microsoft.AspNetCore.App" /> 在 web project中會自動加入這玩意 當然 如果你的Library 打算適用不只web project 就不應該統一包裹 (雖然我是覺得 機率不高) 這可以讓你省去大多數需要主動參考才會有的library 例如文章中提到的 Microsoft.AspNetCore.Configuration 如果你們有內部常用的幾種組合 亦可以建立你們專屬的 runtime store 配置在部屬的機器上 可以讓部屬時避免總是得要那麼大包的去部屬 亦可以統一版本或是 多版本的運行

# by Jeffrey

to 小黑,我目前看到 ASP.NET Core 的官方解法是 Azure Key Vault https://docs.microsoft.com/zh-tw/azure/key-vault/basic-concepts。我的話是沿用原本自己寫的 AES 加密及儲存連線字串的機制,把元件植移成 .NET Standard 或 .NET Core 程式庫繼續使用,好處是同一台主機上 ASP.NET WebForm、MVC 與 ASP.NET Core 可共用同一套連線字串。

# by Jimmy

取appSetting的方式,我們改了幾個版本,目前版本是先建立一個強型別的AppSetting.cs,在Startup(IConfiguration configuration)將configuration傳入AppSetting public Startup(IConfiguration configuration) { Configuration = configuration; AppSetting appSetting = new AppSetting(Configuration); } public IConfiguration Configuration { get; private set; } AppSetting的Constructors中綁定 public AppSetting(IConfiguration configuration) { configuration.Bind(this); } public static string VarName { get; set; } Console程式則在Main中建立configration,並傳入AppSetting IConfigurationRoot configuration = new ConfigurationBuilder() .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) .AddJsonFile("appsettings.json") .Build(); AppSetting appSetting = new AppSetting(configuration); 取值時直接使用AppSetting.VarName 少了其它地方需要透過建構式參數取得這個步驟 整體用起來的體驗會跟以前的取值方式比較像,另外多了自動綁定的強型別避免錯字

Post a comment