1. 程式人生 > >[譯]ASP.NET Core 依賴注入深入討論

[譯]ASP.NET Core 依賴注入深入討論

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

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

舉個例子,這裡我們有一個介面和它的實現類:

public interface IDataService
{
    IList<DataClass> GetAll();
}

public class DataService : IDataService
{
    public IList<DataClass> GetAll()
    {
        //Get data...
        return data;
    }
}

如果另一個服務依賴於DataService,那麼它們依賴於特定的實現,測試這樣的服務可能會非常困難。如果該服務依賴於IDataService,那麼它們只關心介面提供的契約。實現什麼並不重要,它使我們能夠通過一個模擬實現來測試服務的行為。

服務生命週期

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

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

  • Singleton
  • Scoped
  • Transient

Singleton意味著只會建立一個例項,該例項在需要它的所有元件之間共享。因此始終使用相同的例項。

Scoped意味著每個作用域建立一個例項。作用域是在對應用程式的每個請求上建立的,因此,任何註冊為Scoped的元件每個請求都會建立一次。

Transient每次請求時都會建立瞬態元件,並且永遠不會共享。

理解這一點非常重要,如果將元件A註冊為單例,則它不能依賴於具有ScopedTransient生命週期的元件。總而言之:

元件不能依賴比自己的生命週期小的元件。

違反這條規則的後果顯而易見,依賴的元件可能會在依賴項之前釋放。

通常,您希望將元件(如應用程式範圍的配置容器)註冊為Singleton

。資料庫訪問類(如 Entity Framework 上下文)建議使用Scoped,以便可以重複使用連線。但是如果您想並行執行任何東西,請記住 Entity Framework 上下文不能由兩個執行緒共享。如果您需要這樣做,最好將上下文註冊為Transient,這樣每個元件都有自己的上下文例項而且可以並行執行。

服務註冊

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

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

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

這行程式碼將DataService新增到服務集合中。服務型別設定為IDataService,因此如果請求了該型別的例項,則它們將獲得DataService的例項。生命週期也設定為Transient,這樣每次都會建立一個新例項。

ASP.NET Core 提供了很多擴充套件方法,使註冊各種生命週期的服務和其他設定更加方便。

下面是使用擴充套件方法的更簡單的示例:

services.AddTransient<IDataService, DataService>();

是不是更簡單一點?封裝後它當然更容易呼叫,這樣做更簡單。對於不同的生命週期,也有類似的擴充套件方法,你也許可以猜到它們的名字。

如果願意,您也可以在使用單一型別註冊(實現型別=服務型別):

services.AddTransient<DataService>();

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

實現工廠

在一些特殊情況下,您可能想要接管某些服務的例項化。在這種情況下,您可以在服務描述符上註冊一個實現工廠(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>()來獲取在服務集合中註冊的依賴項。

區別在於GetService<T>()如果找不到T型別服務,則返回nullGetRequiredService<T>()如果找不到它,則會引發InvalidOperationException異常。

單例作為常量註冊

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

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

它允許一個非常有趣的場景,假設DataService實現兩個介面。如果我們這樣做:

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的末尾執行此操作,以便在此之前確保已經註冊了所有依賴項。

注入

我們已經註冊了我們的元件,現在我們就可以實際使用它們了。

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

舉個例子,我們來做一個基本的日誌記錄中介軟體元件:

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");
    }
}

在中介軟體中注入元件有三種不同的方式:

  • 建構函式
  • Invoke方法引數
  • 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");
    }
}

中介軟體在應用的整個生命週期中僅例項化一次,因此通過建構函式注入的元件對於所有通過的請求都是相同的

作為Invoke方法的引數注入的元件是中介軟體絕對必需的,如果它找不到要注入的IDataService,它將引發InvalidOperationException異常。

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

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

如果它被註冊為Scopedsvc2svc3將會是同一個例項,但不同的請求會得到不同的例項。

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

每種方法的用例:

  • 建構函式:所有請求都需要的單例(Singleton)元件
  • Invoke引數:在請求中總是必須的作用域(Scoped)和瞬時(Transient)元件
  • RequestServices:基於執行時資訊可能需要或可能不需要的元件

如果可能的話,我會盡量避免使用RequestServices,並且只在中介軟體必須能夠在缺少某些元件一樣可以執行的情況下才使用它。

Startup類

Startup類的建構函式中,您至少可以注入IHostingEnvironmentILoggerFactory。它們是官方文件中提到的僅有兩個介面。可能有其他的,但我不知道。

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()在管道上註冊簡單中介軟體,則不能使用建構函式注入。事實上,通過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請求元件。

在MVC Core中注入

在MVC中進行依賴注入的最常見方法是建構函式注入。

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

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 上新增引數,並使用FromServicesAttribute特性對其進行裝飾來注入所需的服務,這會指示 MVC Core 從服務集合中獲取它,而不是嘗試對其進行模型繫結。

Razor 檢視

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

@using Microsoft.AspNetCore.Mvc.Localization
@inject IViewLocalizer Localizer

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

請注意,不應濫用此機制將本應該來自控制器的資料帶入檢視。

Tag helper

建構函式注入也適用於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過濾器也支援建構函式注入,以及有權訪問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");
    }
}

但是,通過建構函式注入我們不能像往常一樣在控制器上新增特性,因為它在執行的時候必須要獲得依賴項。

這裡我們有兩種方式可以將其新增到控制器或 Action 級別:

[TypeFilter(typeof(TestActionFilter))]
public class HomeController : Controller
{
}
// or
[ServiceFilter(typeof(TestActionFilter))]
public class HomeController : Controller
{
}

以上這兩種方式關鍵的區別是TypeFilterAttribute會先找出過濾器的依賴項並通過DI獲取它們,然後建立過濾器。另一方面,ServiceFilterAttribute則是直接嘗試從服務集合中尋找過濾器!

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

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<TestActionFilter>();
}

現在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,而不需要通過呼叫方法來傳遞它。

結論

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

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

連結

相關推薦

[]ASP.NET Core 依賴注入深入討論

這篇文章我們來深入探討 ASP.NET Core、MVC Core 中的依賴注入,我們將示範幾乎所有可能的操作把依賴項注入到元件中。 依賴注入是 ASP.NET Core 的核心,它能讓您應用程式中的元件增強可測試性,還使您的元件只依賴於能夠提供所需服務的某些元件。 舉個例子,這裡我們有一個介面和它的實現類

深入理解ASP.NET Core依賴注入

概述        ASP.NET Core可以說是處處皆注入,本文從基礎角度理解一下原生DI容器,及介紹下怎麼使用並且如何替換官方提供的預設依賴注入容器。 什麼是依賴注入        百度百科中對於依賴注入的定義:控制反轉(Inversion of Control,縮寫為IoC),是面向物件程式設

ASP.NET Core 依賴注入最佳實踐與技巧[]

# ASP.NET Core 依賴注入最佳實踐與技巧 > 原文地址:https://medium.com/volosoft/asp-net-core-dependency-injection-best-practices-tips-tricks-c6e9c67f9d96 [正(ke)確(xue)上(shan

ASP.NET Core依賴注入&AutoFac

1. 前言 關於IOC模式和DI技術,網上已經有很多相關的探討,再次就不過多贅述了,只是簡單介紹一下它們的概念 控制反轉(IoC/Inverse Of Control):   呼叫者將建立例項的控制權交給IOC容器,由容器建立,所以稱為控制反轉。 依賴注入(DI/Dependence

瞭解ASP.NET Core 依賴注入,看這篇就夠了 於2017年11月6日由jesseliu釋出

var services = new ServiceCollection() .AddScoped<IOperationScoped, Operation>() .AddTransient<IOperationTransient, Operation>() .AddSingleto

ASP.NET Core 依賴注入(DI)

  ASP.NET Core的底層設計支援和使用依賴注入。ASP.NET Core 應用程式可以利用內建的框架服務將服務注入到啟動類的方法中,並且應用程式服務也可以配置注入。由ASP.NET Core 提供的預設服務容器提供了最小功能集,並不是取代其他容器。   1.淺談依賴注入   依賴注入(Depen

ASP.NET Core 依賴注入基本用法

ASP.NET Core 依賴注入 ASP.NET Core從框架層對依賴注入提供支援。也就是說,如果你不瞭解依賴注入,將很難適應 ASP.NET Core的開發模式。本文將介紹依賴注入的基本概念,並結合程式碼演示如何在 ASP.NET Core中使用依賴注入。 什麼是依賴注入? 百度百科對於依賴注入的介紹:

ASP.NET Core依賴注入(DI)

ASP.NET Core允許我們指定註冊服務的生存期.服務例項將根據指定的生存時間自動處理.因此,我們無需擔心清理此依賴關係,他將由ASP.NET Core框架處理.有如下三種類型的生命週期. 關於依賴注入通俗易懂的內容大家可以看一下我上一篇文章 [.NET IoC模式依賴反轉(DIP)、控制反轉(Ioc)、

ASP.NET Core依賴注入初識與思考

> [文章首發地址](https://mp.weixin.qq.com/s/T1EImsoedwDy_ju9pLpbwg) # 一、前言 在上一篇中,我們講述了什麼是控制反轉(IoC)以及通過哪些方式實現的。這其中,我們明白了,**控制反轉(IoC)** 是一種軟體設計的模式,指導我們設計出更優良,更具有鬆

(2)Asp.Net Core 依賴關係注入(服務)

1.前言 面向物件設計(OOD)裡有一個重要的思想就是依賴倒置原則(DIP),並由該原則牽引出依賴注入(DI)、控制反轉(IOC)及其容器等老生常談的概念,初學者很容易被這些概念搞暈(包括我在內),在學習Core依賴注入服務之前,下面讓我們先了解下依賴倒置原則(DIP)、依賴注入(DI)、控制反轉(IOC)

【轉載】ASP.NET Core 依賴註入

microsoft 銷毀 分享圖片 ros inview 我們 col info 服務 本文轉自:http://www.jessetalk.cn/2017/11/06/di-in-aspnetcore/ 為什麽要寫這個博客 DI在.NET Core裏面被提到了一個非常重

【轉】ASP.NET Core 依賴註入

接口 provide ddt 應該 ML 大型 RF zid yui DI在.NET Core裏面被提到了一個非常重要的位置, 這篇文章主要再給大家普及一下關於依賴註入的概念,身邊有工作六七年的同事還個東西搞不清楚。另外再介紹一下.NET Core的DI實現以及對實例生命

[]ASP.NET Core中使用MediatR實現命令和中介者模式

在本文中,我將解釋命令模式,以及如何利用基於命令模式的第三方庫來實現它們,以及如何在ASP.NET Core中使用它來解決我們的問題並使程式碼簡潔。因此,我們將通過下面的主題來進行相關的講解。 什麼是命令模式? 命令模式的簡單例項以及中介者模式的簡單描述 MVC中的瘦控制器是什麼?我們是如如何實現使控制器

ASP.NET CORE依賴註入&AutoFac

project eof 簡潔 重新 每次 imp sum unity builder 1. 前言 關於IOC模式和DI技術,網上已經有很多相關的探討,再次就不過多贅述了,只是簡單介紹一下它們的概念 控制反轉(IoC/Inverse Of Control): 調用者將創

ASP.NET Core on K8S深入學習(1)K8S基礎知識與叢集搭建

在上一個小系列文章《ASP.NET Core on K8S學習初探》中,通過在Windows上通過Docker for Windows搭建了一個單節點的K8S環境,並初步嘗試將ASP.NET Core WebAPI專案部署到了K8S,把玩了一下快速部署和例項伸縮。這個系列開始,會繼續學習K8S以及在Linux

ASP.NET Core on K8S深入學習(2)部署過程解析與Dashboard

上一篇《K8S叢集部署》中搭建好了一個最小化的K8S叢集,這一篇我們來部署一個ASP.NET Core WebAPI專案來介紹一下整個部署過程的執行機制,然後部署一下Dashboard,完成視覺化管理。本篇已加入了《.NET Core on K8S學習實踐系列文章索引》,更多內容請到索引中檢視。 一、部署示

ASP.NET Core on K8S深入學習(3)Deployment

上一篇《部署過程解析與安裝Dashboard》中我們瞭解K8S的部署過程,這一篇我們來了解一下K8S為我們提供的幾種應用執行方式:Deployment、DaemonSet與Job,它們是Kubernetes最重要的核心功能提供者。考慮到篇幅和更新速度,我將其分為兩篇文章,本篇會主要介紹Deployment,主

ASP.NET Core on K8S深入學習(3-2)DaemonSet與Job

本篇已加入《.NET Core on K8S學習實踐系列文章索引》,可以點選檢視更多容器化技術相關係列文章。 上一篇《3-1 Deployment》中介紹了Deployment,它可以滿足我們大部分時候的應用部署(無狀態服務類容器),但是針對一些特殊的場景應用,就可以用到今天介紹的DaemonSet和Job

ASP.NET Core on K8S深入學習(4)你必須知道的Service

本篇已加入《.NET Core on K8S學習實踐系列文章索引》,可以點選檢視更多容器化技術相關係列文章。 前面幾篇文章我們都是使用的ClusterIP供叢集內部訪問,每個Pod都有一個自己的IP地址,那麼問題來了:當控制器使用新的Pod替代發生故障的Pod時又或者增加新的副本Pod時,新Pod會分配到新

ASP.NET Core on K8S深入學習(5)Rolling Update

本篇已加入《.NET Core on K8S學習實踐系列文章索引》,可以點選檢視更多容器化技術相關係列文章。 一、什麼是Rolling Update?   為了服務升級過程中提供可持續的不中斷的服務,K8S提供了Rolling Update機制,它可以使得服務近乎無縫地平滑升級,即在不停止對外服務的前提下