只有累積,沒有奇蹟

2021年11月3日 星期三

[NETCore] ASP.NET Core 使用強型別取代 IOption 注入配置

前言
之前的 如何取得 appsettings.json 組態設定 文章中有介紹在 ASP.NET Core 中透過 IOptions 方法取得設定檔的方法,在需要用到的地方注入 IOptions 取得設定類別的資訊,相信使用上並不困難在 MSDN 官方推薦作法也是如此,但如果開發一陣子之後可以發現到處都是 IOptios 類別,這篇文章介紹如何使用擴充 IServiceCollection 的方法來降低對 IOptios 的依賴,若有問題或是錯誤的地方歡迎各方高手大大一起討論或給予指導

IOption
之首先先來簡單回顧  IOptions<T>  的傳統用法,透過微軟 MSDN 中 IOptions 介紹得知要使用前需先引用 Microsoft.Extensions.Options,為了方便快速了解差異性,這裡建立一個 ASP.NET Core Web 應用程式範例來說明,在新增完應用程式後在 appsetting.json 加入自己定義的  mySettings  設定資訊提供 Name 以及 Title 屬性
"MySettings": {
    "Name": "Marcus",
    "Title": "9527"
  } 
接著要取得設定檔的內容,這邊建立與 Config 內容欄位相同的強型別的 class 物件
public class MySettings
{
    public string Name { get; set; }
    public string Title { get; set; }
} 
在 ConfigureServices 中加入下列代碼,用意是將 appsettinss.json 中的資訊加載到 MySettings 中
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddOptions();
    services.Configure<MySettings>(Configuration.GetSection("MySettings"));
} 
接著就可以在 Value Controll 使用 IOptions<T> 取得設定檔的資訊,代碼如下
public class ValuesController : ControllerBase
{
    public MySettings _mySettings { get; set; }

    public ValuesController(IOptions<MySettings> settings)
    {
        _mySettings = settings.Value;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { _mySettings.Name, _mySettings.Title };
    } 

不使用 IOptions 注入
以上快速地回顧 IOptions<T> 的用法,就可以簡單使用 IOption<T> 取得 appsettings.json 設定資訊的方法,但是這意味著你的代碼與 IOptions 有著強制依賴的關係,你有多少 Controller 就需要在各別的 Controller 中都 using 所需要的 Microsoft.Extensions.Options,除非 appsettings.json 中的設定很常異動需要進行重新載入( 這時可以使用  IOptionMonitor  而不是 IOptions ),否則大部分的使用情境中 config 設定都是較少異動的,參考此文章之後有了新的解法,我們可以新增一個類別並針對 IServiceCollection 加入擴充方法,代碼如下
public static class ServiceCollectionExtensions
{
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration) where TConfig : class, new()
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));

        var config = new TConfig();
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }
} 
這裡我們在 startup.cs 時手動使用  Microsoft.Extensions.Configuration.Binder  來綁定設定檔,並指定服務容器生命週期 (LifeTime) 為 Singleton,在 ASP.NET Core DI 預設提供的 Lifetime 有下列三種
  • Transient : 每次請求時都會產生新的 Instance
  • Scoped : 每個 http Request 都會產生一份 Instance
  • Singleton : 整個 Application 只會有一份 Instance
可以依據所需要情境作調整,如果想了解差異可以參考之前的文章 : 傳送門 

新增完擴充方法之後,接著下一步就是在 ConfigureServices 改用新增的擴充方法 confugurePOCO
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    services.AddOptions();
    services.ConfigurePOCO<MySettings>(Configuration.GetSection("MySettings"));
    //services.Configure<MySettings>(Configuration.GetSection("MySettings"));
} 
接著在回到要使用的地方也就是範例的 ValueController, 將 IOptions 依賴移除改為強行別的 MySettings
public MySettings _mySettings { get; set; }

public ValuesController(MySettings settings)
{
    _mySettings = settings;
} 
在重新執行應用程式,可以發現應用程式執行正常無誤
如果你對於擴充方法很熟悉,也可以針對自己的需求來自訂所需的方法簽章,例如新增一個 TConfig 參數做為繫結設定檔代碼如下
public static class ServiceCollectionExtensions
{
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration, TConfig config) where TConfig : class
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        if (config == null) throw new ArgumentNullException(nameof(config));
 
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    }
} 
使用方式先 new 之後作為參數傳入
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
 
    var mySettings = new MySettings("foo"); 
    services.ConfigurePOCO(Configuration.GetSection("MySettings"), mySettings);
} 
或者是新增 Func<TConfig> 參數透過委派方法新增 TConfig 的 instance
public static class ServiceCollectionExtensions
{
    public static TConfig ConfigurePOCO<TConfig>(this IServiceCollection services, IConfiguration configuration, Func<TConfig> pocoProvider) where TConfig : class
    {
        if (services == null) throw new ArgumentNullException(nameof(services));
        if (configuration == null) throw new ArgumentNullException(nameof(configuration));
        if (pocoProvider == null) throw new ArgumentNullException(nameof(pocoProvider));

        var config = pocoProvider();
        configuration.Bind(config);
        services.AddSingleton(config);
        return config;
    } 
}
使用方式如下
public void ConfigureServices(IServiceCollection services)
{
    //...
    services.ConfigurePOCO(Configuration.GetSection("MySettings"), () => new MySettings("foo"));
    //...
} 
如果想了解更多細節,可以參考 Strongly typed configuration in ASP.NET Core without IOptions<T> 取得更多資訊,希望透過以上的介紹,可以讓跟我有一樣困擾的開發者得到新的作法,Hope it helps :)

1 則留言:

Copyright © m@rcus 學習筆記 | Powered by Blogger

Design by Anders Noren | Blogger Theme by NewBloggerThemes.com