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

thz發表於2019-05-26

ASP.NET Core 依賴注入

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

什麼是依賴注入?

百度百科對於依賴注入的介紹:

控制反轉(Inversion of Control,縮寫為IoC),是物件導向程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。

從百科的介紹中可以看出,依賴注入和控制反轉是一回事,依賴注入是一種新的設計模式,通過正確使用依賴注入的相關技術,可以降低系統耦合度,增加系統的可擴充套件性。

我們來看一個例子:

public interface IInterfaceA
{ }

public interface IInterfaceB
{ }

public class ClassA : IInterfaceA
{
    private IInterfaceB B { get; set; }

    public ClassA(IInterfaceB b)
    {
        this.B = b;
    }
}

public class ClassB : IInterfaceB
{ }

這個時候,如果我們想要獲取IInterfaceA的例項,如果不採用依賴注入,我們的做法通常是這樣的:

IInterfaceB b = new ClassB();
IInterfaceA a = new ClassA(b);

這個時候IInterfaceA的控制權,在例項化的時候就已經被限定死了,沒有任何想象空間,就是ClassA的例項,並且我們還要手工的初始化IInterfaceB,同樣B的控制權也被限定死了。這樣的程式碼毫無設計、也極不利於擴充套件。

如果採用依賴注入,我們看一下程式碼:

var a = container.GetService<IInterfaceA>();

這個時候介面A和B的控制權是由容器來控制的,我們可以通過向容器中注入不同的介面實現來擴充套件系統的靈活性,由於將控制權交給了IoC容器,我們還可以通過配置的方式靈活的控制物件的生命週期,這一點也是手工建立物件無法實現的。

控制反轉的關係圖如下(圖片來源於官網):
image

ASP.NET Core中的依賴注入

上面介紹了依賴注入的基本概念,那麼在 ASP.NET Core中,我們該如何使用依賴注入呢?在 ASP.NET Core中已經內建了一套依賴注入的容器,我們可以直接使用。

在Startup.ConfigureServices中新增我們要註冊的服務和實現,新增的時候可以對服務的生命週期進行相應的配置,然後就可以在PageModel、Controller、Views等需要的地方使用了。

下面的示例將演示如何註冊服務,程式碼來源於官網。首先要定義一個服務介面,並實現這個介面:

public interface IMyDependency
{
    Task WriteMessage(string message);
}

public class MyDependency : IMyDependency
{
    private readonly ILogger<MyDependency> _logger;

    public MyDependency(ILogger<MyDependency> logger)
    {
        _logger = logger;
    }

    public Task WriteMessage(string message)
    {
        _logger.LogInformation(
            "MyDependency.WriteMessage called. Message: {MESSAGE}", 
            message);

        return Task.FromResult(0);
    }
}

然後我們進行服務註冊:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();
    services.AddMvc();
}

這裡我們新增了IMyDependency的註冊,同時也新增了使用Mvc所需要的服務的註冊。這裡有兩個問題需要說明:

  • AddScoped是新增一個服務註冊,Scoped是該服務的生命週期,表示按照作用於建立該服務,如果作用域中多次使用到該服務,則只建立一個物件。比如每一個HTTP請求都是一個作用域,那麼在這個請求處理過程中,容器只會建立一個物件。與Scoped對應的還有其它的生命週期,我們將服務的生命週期列舉如下:

    • Transient:瞬時服務,表示每次使用都會建立新的物件
    • Scoped:作用域服務,表示每次請求只建立一個物件。這裡需要特殊說明一下,如果你的服務是一箇中介軟體,不受此約束,因為中介軟體都是強制單例的。如果要在中介軟體中使用Scoped服務,則需要將服務注入到Invoke或InvokeAsync方法的引數中,此處可以參考 ASP.NET Core 中介軟體基本用法
    • Singleton:單例服務,表示每個應用程式域只會建立一個實力。
  • 基於約定,ASP.NET Core推薦我們採用類似於Add{SERVICE_NAME}的方式新增服務的註冊,比如services.AddMvc(),這種方式可以通過擴充套件方法來實現,程式碼如下:
namespace Microsoft.Extensions.DependencyInjection
{
    public static partial class MyDependencyExtensions
    {
        public static IServiceCollection AddMyDependency(this IServiceCollection services)
        {
            return services.AddScoped<IMyDependency, MyDependency>();
        }
    }
}

使用依賴注入

在瞭解了依賴注入的基本用法之後,我們現在來了解一下如何將服務注入到Controller、Views中。

在控制器中注入服務

最常規的用法是採用建構函式注入的方式,將一個服務注入到控制器中,程式碼如下:

public class DefaultController : Controller
{
    private readonly ILogger<DefaultController> logger;

    public DefaultController(ILogger<DefaultController> logger)
    {
        this.logger = logger;
    }
}

建構函式注入是最常用的注入方式,這種方式要求依賴者提供公有的建構函式,並將依賴項通過建構函式的方式傳入依賴者,完成對依賴項的賦值。

除此之外,還可以通過引數注入的方式,將依賴項注入到Action中,這裡使用到FromServices特性:

public IActionResult Index([FromServices]ILogger<DefaultController> logger)
{
    throw new NotImplementedException();
}

ASP.NET Core 提供了這種支援,但是作者並不推薦這種操作

在檢視中注入服務

ASP.NET Core 支援將依賴關係注入到檢視,程式碼如下:

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
@{
   string myValue = Configuration["root:parent:child"];
   ...
}

上面的程式碼演示了將IConfiguration服務注入到檢視中,從而實現在檢視中讀取配置的功能。

有時候將服務注入到檢視中會很有用(例如本地化),但是作者也並不是很推薦這種做法,因為這樣做容易造成檢視和控制器的邊界不清晰。

在PageModel中注入服務

在PageModel中注入服務的方式,與在Controller中注入服務的方式相似:

public class IndexModel : PageModel
{
    private readonly IMyDependency _myDependency;

    public IndexModel(IMyDependency myDependency)
    {
        _myDependency = myDependency;
    }
}

在main方法中獲取服務

public static void Main(string[] args)
{
    var host = CreateWebHostBuilder(args).Build();

    using (var serviceScope = host.Services.CreateScope())
    {
        var services = serviceScope.ServiceProvider;

        try
        {
            var serviceContext = services.GetRequiredService<MyScopedService>();
            // Use the context here
        }
        catch (Exception ex)
        {
            var logger = services.GetRequiredService<ILogger<Program>>();
            logger.LogError(ex, "An error occurred.");
        }
    }

    host.Run();
}

在HttpContext.RequestServices中獲取服務

這種方式不利於測試,不推薦此種用法。

雖然優先推薦通過建構函式的方式注入來獲取服務,但是很難避免有些時候需要手工獲取服務,在使用手工獲取服務的時候,我們應當從HttpContext.RequestServices中獲取。

使用第三方依賴注入框架

ASP.NET Core內建的依賴注入框架功能有限,當我們想使用第三方框架的特性時,我們可以替換預設依賴注入框架。

ASP.NET Core內建的依賴注入框架未包含的特性:

  • 屬性注入
  • 基於名稱的注入
  • 子容器
  • 自定義生命週期管理
  • 對lazy物件初始化的Func支援

如果要是用這些功能,我們可以使用第三方框架。本文采用官方文件中的Autofac框架。

  • 首先新增 Autofac、Autofac.Extensions.DependencyInjection 的引用
  • 在Startup.ConfigureServices中配置容器,並返回IServiceProvider。在使用第三方容器時,必須返回IServiceProvider。
public IServiceProvider ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
    // Add other framework services

    // Add Autofac
    var containerBuilder = new ContainerBuilder();
    containerBuilder.RegisterModule<DefaultModule>();
    containerBuilder.Populate(services);
    var container = containerBuilder.Build();
    return new AutofacServiceProvider(container);
}
  • 配置Autofac的Module,用來註冊服務等
public class DefaultModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<CharacterRepository>().As<ICharacterRepository>();
    }
}

參考資料

相關文章