依赖注入在 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 版本问题。

参考资料

相关文章
相关标签/搜索