ASP.NET Core應用的7種依賴注入方式
小編:啊南 40閱讀 2020.11.09
構成HostBuilderContext上下文的兩個核心對象(表示配置的IConfiguration對象和表示承載環境的IHostEnvironment對象)可以直接注入Startup構造函數中進行消費。由于ASP.NET Core應用中的承載環境通過IWebHostEnvironment接口表示,IWebHostEnvironment接口派生于IHostEnvironment接口,所以也可以通過注入IWebHostEnvironment對象的方式得到當前承載環境相關的信息。
我們可以通過一個簡單的實例來驗證針對Startup的構造函數注入。如下面的代碼片段所示,我們在調用IWebHostBuilder接口的Startup<TStartup>方法時注冊了自定義的Startup類型。在定義Startup類型時,我們在其構造函數中注入上述3個對象,提供的調試斷言不僅證明了3個對象不為Null,還表明采用IHostEnvironment接口和IWebHostEnvironment接口得到的其實是同一個實例。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder.UseStartup<Startup>()) .Build() .Run(); } } public class Startup { public Startup(IConfiguration configuration, IHostEnvironment hostingEnvironment,IWebHostEnvironment webHostEnvironment) { Debug.Assert(configuration != null); Debug.Assert(hostingEnvironment != null); Debug.Assert(webHostEnvironment != null); Debug.Assert(ReferenceEquals(hostingEnvironment, webHostEnvironment)); } public void Configure(IApplicationBuilder app) { } }二、在Startup類型的Configure方法中注入
依賴服務還可以直接注入用于注冊中間件的Configure方法中。如果構造函數注入還可以對注入的服務有所選擇,那么對于Configure方法來說,通過任意方式注冊的服務都可以注入其中,包括通過調用IHostBuilder、IWebHostBuilder和Startup自身的ConfigureServices方法注冊的服務,還包括框架自行注冊的所有服務。
如下面的代碼代碼片段所示,我們分別調用IWebHostBuilder和Startup的ConfigureServices方法注冊了針對IFoo接口和IBar接口的服務,這兩個服務直接注入Startup的Configure方法中。另外,Configure方法要求提供一個用來注冊中間件的IApplicationBuilder對象作為參數,但是對該參數出現的位置并未做任何限制。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder .UseStartup<Startup>() .ConfigureServices(svcs => svcs.AddSingleton<IFoo, Foo>())) .Build() .Run(); } } public class Startup { public void ConfigureServices(IServiceCollection services) => services.AddSingleton<IBar, Bar>(); public void Configure(IApplicationBuilder app, IFoo foo, IBar bar) { Debug.Assert(foo != null); Debug.Assert(bar != null); } }三、在中間件類型構造函數中注入
ASP.NET Core請求處理管道最重要的對象是用來真正處理請求的中間件。由于ASP.NET Core在創建中間件對象并利用它們構建整個請求處理管道時,所有的服務都已經注冊完畢,所以任何一個注冊的服務都可以注入中間件類型的構造函數中。如下所示的代碼片段體現了針對中間件類型的構造函數注入。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder .ConfigureServices(svcs => svcs .AddSingleton<FoobarMiddleware>() .AddSingleton<IFoo, Foo>() .AddSingleton<IBar, Bar>()) .Configure(app => app.UseMiddleware<FoobarMiddleware>())) .Build() .Run(); } } public class FoobarMiddleware : IMiddleware { public FoobarMiddleware(IFoo foo, IBar bar) { Debug.Assert(foo != null); Debug.Assert(bar != null); } public Task InvokeAsync(HttpContext context, RequestDelegate next) { Debug.Assert(next != null); return Task.CompletedTask; } }四、在中間件類型的Invoke/InvokeAsync方法中注入
如果采用基于約定的中間件類型定義方式,注冊的服務還可以直接注入真正用于處理請求的InvokeAsync方法或者Invoke方法中。另外,將方法命名為InvokeAsync更符合TAP(Task-based Asynchronous Pattern)編程模式,之所以保留Invoke方法命名,主要是出于版本兼容的目的。如下所示的代碼片段展示了針對InvokeAsync方法的服務注入。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder .ConfigureServices(svcs => svcs .AddSingleton<IFoo, Foo>() .AddSingleton<IBar, Bar>()) .Configure(app => app.UseMiddleware<FoobarMiddleware>())) .Build() .Run(); } } public class FoobarMiddleware { private readonly RequestDelegate _next; public FoobarMiddleware(RequestDelegate next) => _next = next; public Task InvokeAsync(HttpContext context, IFoo foo, IBar bar) { Debug.Assert(context != null); Debug.Assert(foo != null); Debug.Assert(bar != null); return _next(context); } }
雖然約定定義的中間件類型和Startup類型采用了類似的服務注入方式,它們都支持構造函數注入和方法注入,但是它們之間有一些差別。中間件類型的構造函數、Startup類型的Configure方法和中間件類型的Invoke方法或者InvokeAsync方法都具有一個必需的參數,其類型分別為RequestDelegate、IApplicationBuilder和HttpContext,對于該參數在整個參數列表的位置,前兩者都未做任何限制,只有后者要求表示當前請求上下文的參數HttpContext必須作為方法的第一個參數。按照上述約定,如下這個中間件類型FoobarMiddleware的定義是不合法的,但是Starup類型的定義則是合法的。對于這一點,筆者認為可以將這個限制放開,這樣不僅使中間件類型的定義更加靈活,還能保證注入方式的一致性。
public class FoobarMiddleware { public FoobarMiddleware(RequestDelegate next); public Task InvokeAsync(IFoo foo, IBar bar, HttpContext context); } public class Startup { public void Configure(IFoo foo, IBar bar, IApplicationBuilder app); }
對于基于約定的中間件,構造函數注入與方法注入存在一個本質區別。由于中間件被注冊為一個Singleton對象,所以我們不應該在它的構造函數中注入Scoped服務。Scoped服務只能注入中間件類型的InvokeAsync方法中,因為依賴服務是在針對當前請求的服務范圍中提供的,所以能夠確保Scoped服務在當前請求處理結束之后被釋放。
五、在Controller類型的構造函數中注入在一個ASP.NET Core MVC應用中,我們可以在定義的Controller中以構造函數注入的方式注入所需的服務。在如下所示的代碼片段中,我們將IFoobar服務注入到HomeController的構造函數中。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder .ConfigureServices(svcs => svcs .AddSingleton<IFoobar, Foobar>() .AddSingleton<IBar, Bar>() .AddControllersWithViews()) .Configure(app => app .UseRouting() .UseEndpoints(endpoints => endpoints.MapControllers()))) .Build() .Run(); } } public class HomeController : Controller { public HomeController(IFoobar foobar) => Debug.Assert(foobar != null); }六、在Controller的Action方法中注入
借助于ASP.NET Core MVC基于模型綁定的參數綁定機制,我們可以將注冊的服務綁定到目標Action方法的參數上,進而實現針對Action方法的依賴注入。在采用這種類型的注入方式時,我們需要在注入參數上按照如下的方式標注FromServicesAttribute特性,用以確定參數綁定的來源是注冊的服務。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder .ConfigureServices(svcs => svcs .AddSingleton<IFoobar, Foobar>() .AddControllersWithViews()) .Configure(app => app .UseRouting() .UseEndpoints(endpoints => endpoints.MapControllers()))) .Build() .Run(); } } public class HomeController: Controller { [HttpGet("/")] public void Index([FromServices]IFoobar foobar) { Debug.Assert(foobar != null); } }七、在視圖中注入
在ASP.NET Core MVC應用中,我們還可以將服務注冊到現的View中。假設我們定義了如下這個簡單的MVC程序,并定義了一個簡單的HomeController。
class Program { static void Main() { Host.CreateDefaultBuilder().ConfigureWebHostDefaults(builder => builder .ConfigureServices(svcs => svcs .AddSingleton<IFoobar, Foobar>() .AddControllersWithViews()) .Configure(app => app .UseRouting() .UseEndpoints(endpoints => endpoints.MapControllers()))) .Build() .Run(); } } public class HomeController: Controller { [HttpGet("/")] public IActionResult Index() => View(); }
我們為HomeController定義了一個路由指向根路徑(“/”)的Action方法Index,該方法在調用View方法呈現默認的View之前,將注入的IFoo服務以ViewBag的形式傳遞到View中。如下所示的代碼片段是這個Action方法對應View(/Views/Home/Index.cshtml)的定義,我們通過@inject指令注入了IFoobar服務,并將屬性名設置為Foobar,這意味著當前View對象將添加一個Foobar屬性來引用注入的服務。
@inject IFoobar Foobar @ { Debug.Assert(Foobar!= null); }
相關推薦
- ASP.Net Web Page深入探討 一、服務器腳本基礎介紹首先,我們先復習一下Web服務器頁面的基本執行方式:1、 客戶端通過在瀏覽器的地址欄敲入地址來發送請求到服務器端2、 服務器接收到請求之后,發給相應的服務器端頁面(也就是腳本)來執行,腳本產生客戶端的響應,發送回客戶端3、 客戶…
- 支付寶小程序:自定義SDK,alipay.offline.material.image.upload(上傳門店照片和視頻接口)的封裝 在對接會員卡模板創建時,有一條logo_id和background_id必須通過接口(alipay.offline.material.image.upload)上傳圖片來進行展示。由于我使用的是go語言作為后端的開發語言,恰巧官方又沒有封裝,所以只能自己動手。僅供其他沒有SDK語言的小伙伴參考!首先和…