日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

ASP.NET Core依赖注入深入讨论

發(fā)布時間:2023/12/4 asp.net 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ASP.NET Core依赖注入深入讨论 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

這篇文章我們來深入探討ASP.NET Core、MVC Core中的依賴注入,我們將示范幾乎所有可能的操作把依賴項注入到組件中。

依賴注入是ASP.NET Core的核心,它能讓您應用程序中的組件增強可測試性,還使您的組件只依賴于能夠提供所需服務的某些組件。

舉個例子,這里我們有一個接口和它的實現(xiàn)類:

public interface IDataService

{

? ? IList<DataClass> GetAll();

}


public class DataService : IDataService

{

? ? public IList<DataClass> GetAll()

? ? {

? ? ? ? //Get data...

? ? ? ? return data;

? ? }

}

如果另一個服務依賴于DataService,那么它們依賴于特定的實現(xiàn),測試這樣的服務可能會非常困難。如果該服務依賴于IDataService,那么它們只關心接口提供的契約。實現(xiàn)什么并不重要,它使我們能夠通過一個模擬實現(xiàn)來測試服務的行為。

1.服務生命周期

在我們討論如何在實踐中進行注入之前,了解什么是服務生命周期至關重要。當一個組件通過依賴注入請求另一個組件時,它所接收的實例是否對該組件的實例來說是唯一的,這取決于它的生命周期。設置生命周期從而決定組件實例化的次數(shù),以及組件是否共享。

在ASP.NET Core中,內置的DI容器有三種模式:

  • Singleton

  • Scoped

  • Transient

Singleton意味著只會創(chuàng)建一個實例,該實例在需要它的所有組件之間共享。因此始終使用相同的實例。

Scoped意味著每個作用域創(chuàng)建一個實例。作用域是在對應用程序的每個請求上創(chuàng)建的,因此,任何注冊為Scoped的組件每個請求都會創(chuàng)建一次。

Transient每次請求時都會創(chuàng)建瞬態(tài)組件,并且永遠不會共享。

理解這一點非常重要,如果將組件A注冊為單例,則它不能依賴于具有Scoped或Transient生命周期的組件。總而言之:

組件不能依賴比自己的生命周期小的組件。

違反這條規(guī)則的后果顯而易見,依賴的組件可能會在依賴項之前釋放。

通常,您希望將組件(如應用程序范圍的配置容器)注冊為Singleton。數(shù)據庫訪問類(如Entity Framework上下文)建議使用Scoped,以便可以重復使用連接。但是如果您想并行運行任何東西,請記住Entity Framework上下文不能由兩個線程共享。如果您需要這樣做,最好將上下文注冊為Transient,這樣每個組件都有自己的上下文實例而且可以并行運行。

2.服務注冊

注冊服務是在Startup類的ConfigureServices(IServiceCollection)方法中完成的。

這是一個服務注冊的例子:

services.Add(new ServiceDescriptor(typeof(IDataService), typeof(DataService), ServiceLifetime.Transient));

這行代碼將DataService添加到服務集合中。服務類型設置為IDataService,因此如果請求了該類型的實例,則它們將獲得DataService的實例。生命周期也設置為Transient,這樣每次都會創(chuàng)建一個新實例。

ASP.NET Core提供了很多擴展方法,使注冊各種生命周期的服務和其他設置更加方便。

下面是使用擴展方法的更簡單的示例:

services.AddTransient<IDataService, DataService>();

是不是更簡單一點?封裝后它當然更容易調用,這樣做更簡單。對于不同的生命周期,也有類似的擴展方法,你也許可以猜到它們的名字。

如果愿意,您也可以在使用單一類型注冊(實現(xiàn)類型=服務類型):

services.AddTransient<DataService>();

但是呢,當然組件必須取決于具體的類型,所以這可能是不需要的。

2.1.實現(xiàn)工廠

在一些特殊情況下,您可能想要接管某些服務的實例化。在這種情況下,您可以在服務描述符上注冊一個實現(xiàn)工廠(Implementation Factory)。這有一個例子:

services.AddTransient<IDataService, DataService>((ctx) =>

{

? ? IOtherService svc = ctx.GetService<IOtherService>();

? ? //IOtherService svc = ctx.GetRequiredService<IOtherService>();

? ? return new DataService(svc);

});

它使用另一個組件IOtherService實例化DataService。您可以使用GetService<T>()或GetRequiredService<T>()來獲取在服務集合中注冊的依賴項。

區(qū)別在于GetService<T>()如果找不到T類型服務,則返回null;GetRequiredService<T>()如果找不到它,則會引發(fā)InvalidOperationException異常。

2.2.單例作為常量注冊

如果您想自己實例化一個單例,你可以這樣做:

services.AddSingleton<IDataService>(new DataService());

它允許一個非常有趣的場景,假設DataService實現(xiàn)兩個接口。如果我們這樣做:

services.AddSingleton<IDataService, DataService>();

services.AddSingleton<ISomeInterface, DataService>();

我們得到兩個實例,兩個接口都有一個。如果我們打算共享一個實例,這是一種方法:

var dataService = new DataService();

services.AddSingleton<IDataService>(dataService);

services.AddSingleton<ISomeInterface>(dataService);

如果組件具有依賴關系,則可以從服務集合構建服務提供者并從中獲取必要的依賴項:

IServiceProvider provider = services.BuildServiceProvider();


IOtherService otherService = provider.GetRequiredService<IOtherService>();


var dataService = new DataService(otherService);

services.AddSingleton<IDataService>(dataService);

services.AddSingleton<ISomeInterface>(dataService);

請注意,您應該在ConfigureServices的末尾執(zhí)行此操作,以便在此之前確保已經注冊了所有依賴項。

3.注入

我們已經注冊了我們的組件,現(xiàn)在我們就可以實際使用它們了。

在ASP.NET Core中注入組件的典型方式是構造函數(shù)注入,針對不同的場景確實存在其他選項,但構造器注入允許您定義在沒有這些其他組件的情況下此組件不起作用。

舉個例子,我們來做一個基本的日志記錄中間件組件:

public class LoggingMiddleware

{

? ? private readonly RequestDelegate _next;


? ? public LoggingMiddleware(RequestDelegate next)

? ? {

? ? ? ? _next = next;

? ? }


? ? public async Task Invoke(HttpContext ctx)

? ? {

? ? ? ? Debug.WriteLine("Request starting");

? ? ? ? await _next(ctx);

? ? ? ? Debug.WriteLine("Request complete");

? ? }

}


在中間件中注入組件有三種不同的方式:

  • 構造函數(shù)

  • Invoke方法參數(shù)

  • HttpContext.RequestServices

讓我們使用三種全部方式注入我們的組件:

public class LoggingMiddleware

{

? ? private readonly RequestDelegate _next;

? ? private readonly IDataService _svc;


? ? public LoggingMiddleware(RequestDelegate next, IDataService svc)

? ? {

? ? ? ? _next = next;

? ? ? ? _svc = svc;

? ? }


? ? public async Task Invoke(HttpContext ctx, IDataService svc2)

? ? {

? ? ? ? IDataService svc3 = ctx.RequestServices.GetService<IDataService>();

? ? ? ??

? ? ? ? Debug.WriteLine("Request starting");

? ? ? ? await _next(ctx);

? ? ? ? Debug.WriteLine("Request complete");

? ? }

}

中間件在應用的整個生命周期中僅實例化一次,因此通過構造函數(shù)注入的組件對于所有通過的請求都是相同的

作為Invoke方法的參數(shù)注入的組件是中間件絕對必需的,如果它找不到要注入的IDataService,它將引發(fā)InvalidOperationException異常。

第三個通過使用HttpContext請求上下文的RequestServices屬性的GetService<T>()方法來獲取可選的依賴項。RequestServices屬性的類型是IServiceProvider,因此它與實現(xiàn)工廠中的提供者完全相同。如果您打算要求拿到這個組件,可以使用GetRequiredService<T>()。

如果IDataService被注冊為Singleton,我們會在它們中獲得相同的實例。

如果它被注冊為Scoped,svc2和svc3將會是同一個實例,但不同的請求會得到不同的實例。

在Transient的情況下,它們都是不同的實例。

每種方法的用例:

  • 構造函數(shù):所有請求都需要的單例(Singleton)組件

  • Invoke參數(shù):在請求中總是必須的作用域(Scoped)和瞬時(Transient)組件

  • RequestServices:基于運行時信息可能需要或可能不需要的組件

如果可能的話,我會盡量避免使用RequestServices,并且只在中間件必須能夠在缺少某些組件一樣可以運行的情況下才使用它。

3.1.Startup類

在Startup類的構造函數(shù)中,您至少可以注入IHostingEnvironment和ILoggerFactory。它們是官方文檔中提到的僅有兩個接口。可能有其他的,但我不知道。

public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory)

{

? ? ...

}

IHostingEnvironment通常用于為應用程序設置配置。您可以使用ILoggerFactory設置日志記錄。


Configure方法允許您注入已注冊的任何組件。

public void Configure(

? ? IApplicationBuilder app,

? ? IHostingEnvironment env,

? ? ILoggerFactory loggerFactory,

? ? IDataService dataSvc)

{

? ? ...

}

因此,如果在管道配置過程中有需要的組件,您可以在這里簡單地要求它們。

如果使用app.Run()/app.Use()/app.UseWhen()/app.Map()在管道上注冊簡單中間件,則不能使用構造函數(shù)注入。事實上,通過ApplicationServices/?RequestServices是獲取所需組件的唯一方法。

這里有些例子:

IDataService dataSvc2 = app.ApplicationServices.GetService<IDataService>();

app.Use((ctx, next) =>

{

? ? IDataService svc = ctx.RequestServices.GetService<IDataService>();

? ? return next();

});


app.Map("/test", subApp =>

{

? ? IDataService svc1 = subApp.ApplicationServices.GetService<IDataService>();

? ? subApp.Run((context =>

? ? {

? ? ? ? IDataService svc2 = context.RequestServices.GetService<IDataService>();

? ? ? ? return context.Response.WriteAsync("Hello!");

? ? }));

});


app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/test2"), subApp =>

{

? ? IDataService svc1 = subApp.ApplicationServices.GetService<IDataService>();

? ? subApp.Run(ctx =>

? ? {

? ? ? ? IDataService svc2 = ctx.RequestServices.GetService<IDataService>();

? ? ? ? return ctx.Response.WriteAsync("Hello!");

? ? });

});

因此,您可以在配置時通過IApplicationBuilder上的ApplicationServices請求組件,并在請求時通過HttpContext上的RequestServices請求組件。

3.2.在MVC Core中注入

在MVC中進行依賴注入的最常見方法是構造函數(shù)注入。

您可以在任何地方做到這一點。在控制器中,您有幾個選項:

public class HomeController : Controller

{

? ? private readonly IDataService _dataService;


? ? public HomeController(IDataService dataService)

? ? {

? ? ? ? _dataService = dataService;

? ? }


? ? [HttpGet]

? ? public IActionResult Index([FromServices] IDataService dataService2)

? ? {

? ? ? ? IDataService dataService3 = HttpContext.RequestServices.GetService<IDataService>();

? ? ? ??

? ? ? ? return View();

? ? }

}

如果您希望稍后根據運行時決策獲取依賴項,則可以再次使用Controller基類(技術上講,ControllerBase最好)的HttpContext屬性上可用的RequestServices。

您也可以通過在特定的Action上添加參數(shù),并使用FromServicesAttribute特性對其進行裝飾來注入所需的服務,這會指示MVC Core從服務集合中獲取它,而不是嘗試對其進行模型綁定。

Razor視圖

您還可以使用新的關鍵字@inject在Razor視圖中注入組件:

@using Microsoft.AspNetCore.Mvc.Localization

@inject IViewLocalizer Localizer

在這里,我們在_ViewImports.cshtml中注入了一個視圖本地化器,因此我們將它作為Localizer在所有視圖中提供。


請注意,不應濫用此機制將本應該來自控制器的數(shù)據帶入視圖。


Tag helper

構造函數(shù)注入也適用于Tag Helper:

[HtmlTargetElement("test")]

public class TestTagHelper : TagHelper

{

? ? private readonly IDataService _dataService;


? ? public TestTagHelper(IDataService dataService)

? ? {

? ? ? ? _dataService = dataService;

? ? }

}

視圖組件

視圖組件也一樣:

public class TestViewComponent : ViewComponent

{

? ? private readonly IDataService _dataService;


? ? public TestViewComponent(IDataService dataService)

? ? {

? ? ? ? _dataService = dataService;

? ? }


? ? public async Task<IViewComponentResult> InvokeAsync()

? ? {

? ? ? ? return View();

? ? }

}

在視圖組件中也可以獲得HttpContext,因此有權訪問RequestServices。

過濾器

MVC過濾器也支持構造函數(shù)注入,以及有權訪問RequestServices:

public class TestActionFilter : ActionFilterAttribute

{

? ? private readonly IDataService _dataService;


? ? public TestActionFilter(IDataService dataService)

? ? {

? ? ? ? _dataService = dataService;

? ? }


? ? public override void OnActionExecuting(ActionExecutingContext context)

? ? {

? ? ? ? Debug.WriteLine("OnActionExecuting");

? ? }


? ? public override void OnActionExecuted(ActionExecutedContext context)

? ? {

? ? ? ? Debug.WriteLine("OnActionExecuted");

? ? }

}

但是,通過構造函數(shù)注入我們不能像往常一樣在控制器上添加特性,因為它在運行的時候必須要獲得依賴項。

這里我們有兩種方式可以將其添加到控制器或Action級別:

[TypeFilter(typeof(TestActionFilter))]

public class HomeController : Controller

{

}

// or

[ServiceFilter(typeof(TestActionFilter))]

public class HomeController : Controller

{

}

以上這兩種方式關鍵的區(qū)別是TypeFilterAttribute會先找出過濾器的依賴項并通過DI獲取它們,然后創(chuàng)建過濾器。另一方面,ServiceFilterAttribute則是直接嘗試從服務集合中尋找過濾器!

所以,為了使[ServiceFilter(typeof(TestActionFilter))]正常工作,我們需要多一點配置:

public void ConfigureServices(IServiceCollection services)

{

? ? services.AddTransient<TestActionFilter>();

}

現(xiàn)在ServiceFilterAttribute就可以找到過濾器了。

如果您想添加全局過濾器:

public void ConfigureServices(IServiceCollection services)

{

? ? services.AddMvc(mvc =>

? ? {

? ? ? ? mvc.Filters.Add(typeof(TestActionFilter));

? ? });

}

這樣就不需要將過濾器添加到服務集合,它的工作方式就好像您已經在每個控制器上添加了TypeFilterAttribute一樣。

HttpContext

我已經多次提到過HttpContext。如果您想訪問控制器/視圖/視圖組件之外的HttpContext,那怎么辦?例如,要訪問當前登錄用戶的聲明?

您只要簡單地注入IHttpContextAccessor,如下所示:

public class DataService : IDataService

{

? ? private readonly HttpContext _httpContext;


? ? public DataService(IOtherService svc, IHttpContextAccessor contextAccessor)

? ? {

? ? ? ? _httpContext = contextAccessor.HttpContext;

? ? }

? ? //...

}

這樣可以讓您的服務層直接訪問HttpContext,而不需要通過調用方法來傳遞它。

4.結論

相對于Ninject或Autofac等較大、較老的DI框架來說,ASP.NET Core提供的依賴注入容器在功能上比較基本,但它仍然非常適合大多數(shù)需求。

您可以在任何需要的地方注入組件,從而使組件在此過程中更具可測試性。

5.鏈接

  • 在 ASP.NET Core 依賴注入 | Microsoft Docs

  • 控制器中的依賴關系注入 | Microsoft Docs

  • 視圖中的依賴關系注入 | Microsoft Docs


原文:http://www.cnblogs.com/esofar/p/8625619.html


.NET社區(qū)新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com

總結

以上是生活随笔為你收集整理的ASP.NET Core依赖注入深入讨论的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。