【ASP.NET Core】選項類的依賴注入

2022-07-30 12:00:15

咱們繼續上一個話題。先簡單複習一下,根據老周前面文章的介紹,選項類體系的基本套路是通過 IOptionsFactory 來建立選項類範例的。而我們在服務容器(IServiceCollection)上是用Configure、PostConfigure 等擴充套件方法去設定選項類的(設定屬性的值)。設定程式碼並不是立即執行,而是通過委託來讓寫程式碼的人自己設定屬性值,最後向服務容器新增 IConfigureOptions、 IPostConfigureOptions、IValidateOptions 等關聯服務。IOptionsFactory 通過依賴注入獲得上述服務,並使用它們來設定選項類。

本文重點說一下選項類的依賴注入——如何注入到其他型別中。這個當然是依靠建構函式(使用 Invoke 約定的中介軟體類除外)了(GetService、GetRequiredService 等方法也可以)。選項類自身不是直接新增到服務容器中,所以不能用於依賴注入。

我們還得回過頭去看一下 AddOptions 擴充套件方法的原始碼。

   services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
   services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
   services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
   services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
   services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));

其他的忽略不談,我們一般用得多的是這三個:

IOptions:單例模式,全場只建立一個範例。這個適用於只想讀選項類資訊的情形。

IOptionsSnapshot:作用域模式,生命週期和一次HTTP請求相同。這個適用於選項有分組命名的情況,就是同一個選項類有多組設定時。

IOptionsMonitor:這個也是單範例模式。既可用於單組選項類也可用於多組選項類。當與選項類繫結的設定(通常是組態檔,如 appsettings.json)更新時會自動產生通知。此時不用重啟應用程式,重新整理一下頁面就能獲取到新的資訊。

 

接下來就是實戰階段,咱們先準備一個選項類。

    public class DemoOptions
    {
        public string? AppTitle { get; set; }
        public uint MaxInstance { get; set; }
        public bool Locked { get; set; }
    }

這個選項類僅用於演示,我是隨便寫的,沒特殊含義,請不要惡意猜測其實際用途。

接下來就很簡單了。在應用程式初始化時咱們設定一下。

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();   // MVC無V
// 設定選項類
builder.Services.Configure<DemoOptions>(opt =>
{
    opt.AppTitle = "山高皇帝遠,壞事幹翻天";
    opt.MaxInstance = 12;
    opt.Locked = false;
});
var app = builder.Build();
// 新增 MVC 路由
app.MapControllerRoute("Lily", "{controller=Main}/{action=Work}");

app.Run();

隨後,咱們定義個控制器類,就可以用了。

    public class MainController : Controller
    {
        // 欄位
        readonly IOptions<DemoOptions> _myOpt;

        // 建構函式接收賞賜
        public MainController(IOptions<DemoOptions> o)
        {
            _myOpt = o;
        }

        // 操作方法
        public ActionResult Work()
        {
            DemoOptions theOption = _myOpt.Value;
            string s = "App Title: " + theOption.AppTitle +
                       $"\nMax Instance: {theOption.MaxInstance}\n" +
                       $"Locked: {theOption.Locked}";
            return Content(s);
        }
    }

執行後,如果看到以下內容,說明你的程式碼沒寫錯。

 

 

如果,選項有不同的分組。比如叫 g1 和 g2。

builder.Services.Configure<DemoOptions>("g1", opt =>
{
    opt.AppTitle = "鴨梨山小";
    opt.MaxInstance = 5;
    opt.Locked = true;
});
builder.Services.Configure<DemoOptions>("g2", opt =>
{
    opt.AppTitle = "無頭公雞";
    opt.MaxInstance = 15;
    opt.Locked = false;
});

其實,未分組的選項類也是有一個預設組名的,叫空白字串(string.Empty),可從 Options.DefaultName 欄位獲取。

選項分組後,同一個選項類就擁有不同的設定方案。

接下來的使用也不復雜。

    public class GpOptController : Controller
    {
        // 欄位
        IOptionsSnapshot<DemoOptions> _myOpt;
        // 建構函式接受封賞
        public GpOptController(IOptionsSnapshot<DemoOptions> opt)
        {
            _myOpt = opt;
        }

        // 操作方法
        // g 是分組名稱
        public ActionResult GetInfo(string g)
        {
            DemoOptions opt = _myOpt.Get(g);
            return Content(
                    $"App Title: {opt.AppTitle}\n" +
                    $"Max Instance: {opt.MaxInstance}\n" +
                    $"Locked: {opt.Locked}"
                );
        }
    }

注意 GetInfo 方法有個引數 g,用來篩選顯示哪個分組的選項類(g1?g2?)。

假如要顯示 g1 的選項資訊,就請求 http://SBHost:7337/gpopt/getinfo?g=g1。得到結果如下。

 

 同理,g2 的存取URL:https://BugHost/gpopt/getinfo?g=g2。結果如下。

 

 不要存取什麼 g3、g4 的,因為沒有這些分組,將得到一個帶預設屬性值的 DemoOptions 範例。

 

最後,我們來看看 IOptionsMonitor。要想讓 IOptionsMonitor 在關聯的設定更改時獲得更新通知,還需要實現 IOptionsChangeTokenSource,然後將其新增到服務容器中。內部預設實現的類是 ConfigurationChangeTokenSource。當組態檔被更改後會讓 IOptionsMonitor 得到通知。實現 IOptionsChangeTokenSource 介面似乎有些複雜,一般我們不需要這樣做。

接下來我們把前面的程式碼改一下,在應用程式初始化時,用 appsettings.json 檔案中的內容去設定選項類。

builder.Services.Configure<DemoOptions>(builder.Configuration.GetSection("test"));

設定 DemoOptions 類的節點名為「test」。開啟 appsettings.json 檔案,加上這個節點。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "test": {
    "appTitle": "紙糊的飛機",
    "maxInstance": 16,
    "locked": true
  }
}

定義一個控制器類,用於獲取選項類資訊。

    public class WKController : Controller
    {
        // 欄位
        readonly IOptionsMonitor<DemoOptions> _mntOpt;
        // 建構函式接受冊封
        public WKController(IOptionsMonitor<DemoOptions> o)
        {
            _mntOpt = o;
        }

        // 操作方法
        public ActionResult Do()
        {
            DemoOptions opt = _mntOpt.CurrentValue;
            return Content(
                    $"App Title: {opt.AppTitle}\n" +
                    $"Max Instance: {opt.MaxInstance}\n" +
                    $"Locked: {opt.Locked}"
                );
        }
    }

執行應用程式,定位到URL:https://localhost/wk/do,得到如下結果。

 

不用關閉應用程式,開啟 appsettings.json 檔案,把它改一下。

  "test": {
    "appTitle": "茶葉青",
    "maxInstance": 36,
    "locked": true
  }

然後儲存檔案,回面 web 頁面,重新整理一下,就能看到新的設定了。

 

應用程式在不重啟的情況下載入最新的選項設定。 

 

今天的水文到此就結束了,咱們下次再聊。