依賴注入在 dotnet core 中實現與使用:4. 集成 Autofac

本示例使用 .net core 5 rc-1 實現。html

1. 添加 Nuget 包引用

使用 Autofac 固然要添加 Autofac 的 Nuget 包,主要涉及到兩個:git

  • Autofac.Extensions.DependencyInjection 核心支持包
  • Autofac.Extras.DynamicProxy2 AOP 動態代理支持
    若是不須要動態代理的話,只須要添加第一個便可。
dotnet add package Autofac.Extensions.DependencyInjection

2. 配置 Autofac

首先須要須要配置 Autofac 的容器工廠。github

因爲須要使用 Autofac 的容器,因此在構建應用程序的時候,須要使用 Autofac 的服務工廠。主程序 Program 中的 CreateHostBuilder() 方法須要增長一行,修改以後以下所示:web

public static IHostBuilder CreateHostBuilder (string[] args) =>
            Host.CreateDefaultBuilder (args)
            .UseServiceProviderFactory (new AutofacServiceProviderFactory ())
            .ConfigureWebHostDefaults (webBuilder => {
                webBuilder.UseStartup<Startup> ();
            });
}

而後,須要在 Startup() 中配置服務註冊。api

Autofac 的服務工廠會在調用 ConfigureServices() 以後,自動調用名爲 ConfigureContainer() 的方法,通常狀況下,咱們會在這個方法裏面使用 Autofac 來註冊服務。bash

在 Startup 文件中,添加以下的 ConfigureContainer() 方法。ContainerBuilder 是定義在命名空間 Autofac 中的,注意添加對該命名空間的引用。async

using Autofac;

public void ConfigureContainer (ContainerBuilder builder) {
      ......
}

Autofac 提供了各類註冊服務的方法,不是微軟的 Addxxx() 方式,而是 Registerxxx() 方式。
例如,若是咱們已經定義了一個 IDbService 接口,而它的實現類型是 DbService。那麼,註冊服務的形式以下所示:ide

// register type, and enable interceptor injection
 builder.RegisterType<DbService> ().As<IDbService> ()
            .InstancePerLifetimeScope ();

DbService 是註冊在容器中的實現類型,而 As<IDbService> 是在容器中註冊的類型。注入的時候須要使用這個接口類型。InstancePerLifetimeScope() 則是說明它的生命週期是 Scope 類型的。
能夠看到,在 Autofac 中,使用鏈式調用的方式來完成服務註冊。函數

3. 使用 Autofac Module 進行註冊

Autofac 提供了一個名爲 Module 的概念,它支持將一組相關的服務註冊過程進行打包,以簡化配置和部署。
Autofac 提供了名爲 Autofac.IModule 接口,以及一個它的抽象實現類型 Autofac.Module。它的核心是 Load() 方法,用來完成服務的註冊。咱們能夠重載它以實現自定義的服務註冊,該方法的簽名以下:性能

protected virtual void Load(
	ContainerBuilder builder
)

能夠看到該方法提供一樣的 ContainerBuilder 參數來提供服務註冊的支持。
這樣的話,前面的服務註冊能夠轉移到一個 Autofac 的 Module 中來。
咱們能夠定義一個服務註冊類,以下所示:

using Autofac;
using Microsoft.AspNetCore.Mvc;

public class ServiceAutofacModule : Autofac.Module {
    protected override void Load (ContainerBuilder builder) {
           // register type, and enable interceptor injection
           builder.RegisterType<DbService> ().As<IDbService> ()
              .InstancePerLifetimeScope ();
    }
}

而後,將 Startup() 中的 ConfigureContainer() 調整爲以下形式,使用 Module 的方式完成服務註冊。

public void ConfigureContainer (ContainerBuilder builder) {

            // use autofac module 
            builder.RegisterModule<ServiceAutofacModule>();
}

Module 的使用詳見:https://autofaccn.readthedocs.io/en/latest/configuration/modules.html

4. 常見的註冊方式

1. 按照類型進行註冊

// register type, and enable interceptor injection
        builder.RegisterType<DbService> ().As<IDbService> ()
            .InstancePerLifetimeScope ();

2. 按已經引用的程序集註冊

var assembly = assembly.Load ("Domain.Services");
        builder.registerAssemblyType (assembly)
            .AsImplementedInterfaces ()
            .InstancePerLifetimeScope ();

3. 註冊程序集中的某些服務

下面的代碼中,先取得了 ControllerBase 的類型,而後在當前程序集中查找全部派生自 ControllerBase 的 Api 控制器

builder.RegisterAssemblyTypes(typeof(Program).Assembly)
        .Where(t => t.Name.EndsWith("Service"))
        .AsImplementedInterfaces()
        .InstancePerLifetimeScope();

5. 使用屬性注入

Autofac 除了支持構造函數注入,還支持屬性注入,屬性注入會在構造函數注入以後進行。
必需要注意的是,必須在使用屬性注入的服務上進行聲明,
例如,若是 DbService 須要支持屬性注入,那麼須要在註冊該服務的時候進行聲明。

builder.RegisterType<DbService> ().As<IDbService> ()
            .PropertiesAutowired()
            .InstancePerLifetimeScope ();

ASP.NET Core 中,對控制器進行屬性注入的特殊處理

默認狀況下,ASP.NET Core 對於控制器並非從容器中建立的,因此若是你檢查容器中的註冊,是看不到控制器的註冊的。
爲了支持屬性注入,須要讓 ASP.NET Core 將控制器也註冊到容器中。這能夠在 AddControllers() 方法以後,調用 AddControllersAsServices() 來實現。

public void ConfigureServices (IServiceCollection services) {

            services.AddControllers()
                .AddControllersAsServices();
}

而後,咱們須要對控制器添加支持屬性注入的聲明。

既能夠針對單個的控制器類

// make property autowire at one controller
builder.RegisterType<WeatherForecastController>()
            .PropertiesAutowired();

也能夠針對全部的控制器。

// make property autowire at all api controller
 var controllerBaseType = typeof (ControllerBase);
 builder.RegisterAssemblyTypes (typeof (Program).Assembly)
            .Where (t => controllerBaseType.IsAssignableFrom (t) &&
                t != controllerBaseType)
            .PropertiesAutowired ();

6. 使用 AOP 動態代理

使用 AOP 須要以下的 4 個步驟。

1. 定義攔截器

攔截器的接口 IInterceptor 定義在命名空間 Castle.DynamicProxy 中,須要注意的是,它須要添加對 NuGet 包 Autofac.Extras.DynamicProxy 的引用。

dotnet add package Autofac.Extras.DynamicProxy

實現 IInterceptor 接口。

using Castle.DynamicProxy;
using System;

 public class DbServiceInterceptor:IInterceptor  
    {  
        public virtual void Intercept(IInvocation invocation)  
        {  
            Console.WriteLine($"{DateTime.Now}: Before method execting. ");  
            invocation.Proceed();  
            Console.WriteLine($"{DateTime.Now}: After method exected.");  
        }  
    }

2. 註冊攔截器

攔截器也一樣須要註冊到容器中。

// register interceptor
builder.RegisterType<DbServiceInterceptor> ();

3. 啓用攔截器

須要支持攔截器的服務須要啓用攔截器,而後才能使用攔截器。

// register type, and enable interceptor injection
        builder.RegisterType<DbService> ().As<IDbService> ()
            .EnableInterfaceInterceptors ()
            .InstancePerLifetimeScope ();

可使用 EnableInterfaceInterceptors() 或者 EnableClassInterceptors() 擴展方法來啓用攔截器。

EnableInterfaceInterceptors() 建立接口代理來執行攔截,而 EnableClassInterceptors() 則建立目標組件的子類來執行攔截。

4. 使用攔截器

第一種方式是在使用攔截器的服務上,經過特性來聲明使用的攔截器。

using Autofac.Extras.DynamicProxy;
using Castle.DynamicProxy;

[Intercept (typeof (DbServiceInterceptor))]
public class DbService : IDbService {

    public string Say () {
        return "Hello";
    }
}

當使用特性來關聯攔截器的時候,不須要在註冊服務的時候指定攔截器。你只須要啓用,實際的攔截器將被自動發現。

第二種方式是在註冊服務的時候指定,使用 InterceptedBy() 擴展方法。

builder.RegisterType<SomeType>()
       .EnableClassInterceptors()
       .InterceptedBy(typeof(CallLogger));

注意:

  • 使用公共接口
  • 類攔截要求被攔截的方法是虛方法,由於使用了子類代理技術。
  • 經過表達式建立的服務,或者使用實例註冊的服務,不能使用子類方式代理,此時,要使用接口代理。
  • 要使用接口代理,服務必須僅僅經過接口提供服務,爲了最佳的性能,全部此類服務接口必須是註冊的一部分,例如使用 .As 子句。
  • 若是經過 EnableClassInterceptors() 使用了類攔截,則避免使用構造函數選擇器 UsingConstructor()。在使用類攔截的時候,會爲代理類生成新的構造函數以獲取你但願使用的攔截器。若是你使用了 UsingConstructor(),就會跳過此邏輯。致使攔截器不能被使用。

已知問題:

  • 同步方法攔截。Castle 攔截器僅僅支持同步方法攔截。不支持顯式的 async/await 方法。可是,async/await 是 Task 的語法糖,你能夠在攔截器中使用 Task 和 ContinueWith() 之類的方法。 This issue 展現了用法。另外,這些助手類 也使得 async 工做更容易一點。
  • Castle.Core 版本問題。

參考資料

相關文章
相關標籤/搜索