【ASP.NET Core】選項類的依賴注入
- 2022 年 7 月 30 日
- 筆記
- .NET, Asp.Net Core, 個人文章
咱們繼續上一個話題。先簡單複習一下,根據老周前面文章的介紹,選項類體系的基本套路是通過 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 的選項資訊,就請求 //SBHost:7337/gpopt/getinfo?g=g1。得到結果如下。
同理,g2 的訪問URL://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://localhost/wk/do,得到如下結果。
不用關閉應用程式,打開 appsettings.json 文件,把它改一下。
"test": { "appTitle": "茶葉青", "maxInstance": 36, "locked": true }
然後保存文件,回面 web 頁面,刷新一下,就能看到新的配置了。
應用程式在不重啟的情況下載入最新的選項配置。
今天的水文到此就結束了,咱們下次再聊。