.NET Core开发实战课程备忘(4) -- 用Autofac增强容器能力

引入Autofac增强什么能力

  • 基于名称的注入:需要把一个服务按照名称来区分它的不同实现
  • 属性注入:直接把服务注册到某个类的属性里面去,而不需要定义构造函数
  • 子容器:类似原生的scope,但是功能更加丰富
  • 基于动态代理的AOP:当我们需要在服务中注入我们额外的行为的时候

核心扩展点

IServiceProviderFactory<TContainerBuilder>:第三方的依赖注入容器都是使用这个类来作为拓展点,把自己注入到整个框架里面来,也就是我们在使用依赖注入框架的时候,不需要关注谁家的特性谁家接口时怎么样的,我们直接使用官方核心的定义即可,不需要直接依赖这些框架

集成Autofac

  • Autofac.Extensions.DependencyInjection
  • Autofac.Extras.DynamicProxy

代码验证

项目与依赖

创建名字为DependencyInjectionAutofacDemoASP.NET Core项目,类型为API

通过nuget引入以下两个包

1
2
Autofac.Extensions.DependencyInjection
Autofac.Extras.DynamicProxy

在代码中引入Autofac

Program.csCreateDefaultBuilder后面添加以下代码

1
.UseServiceProviderFactory(new AutofacServiceProviderFactory())

UseServiceProviderFactory用来注册第三方容器的入口

Startup中新增ConfigureContainer方法,代码如下:

1
2
3
public void ConfigureContainer(ContainerBuilder builder)
{
}

至此Autofac框架引入完毕,下面要创建测试服务类

创建测试服务

创建测试服务MyService.cs类,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
namespace DependencyInjectionAutofacDemo.Services
{
public interface IMyService
{
void ShowCode();
}
public class MyService : IMyService
{
public void ShowCode()
{
Console.WriteLine($"MyService.ShowCode:{GetHashCode()}");
}
}
public class MyServiceV2 : IMyService
{
public MyNameService MyNameService { get; set; }
public void ShowCode()
{
Console.WriteLine($"MyServiceV2.ShowCode:{GetHashCode()},MyNameService是否为空:{MyNameService==null}");
}
}
public class MyNameService
{
}
}

创建测试拦截器MyInterceptor.cs,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System;
using Castle.DynamicProxy;

namespace DependencyInjectionAutofacDemo.Services
{
public class MyInterceptor:IInterceptor
{
public void Intercept(IInvocation invocation)
{
Console.WriteLine($"Intercept before,Method:{invocation.Method.Name}");
invocation.Proceed();
Console.WriteLine($"Intercept after,Method:{invocation.Method.Name}");
}
}
}
  • IInterceptor 是Autofac面向切面最重要的一个接口,他可以把我们的逻辑注入到方法的切面里面去
  • invocation.Proceed()是指具体方法的执行,如果这句不执行,就相当于把切面方法拦截了,让具体类的方法不执行

获取Autofac根容器

Startup里新增类型为ILifetimeScopeAutofacContainer属性,然后在Configure方法中为这个属性复制为Autofac的根容器,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using System;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Autofac.Extras.DynamicProxy;
using DependencyInjectionAutofacDemo.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace DependencyInjectionAutofacDemo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}

public void ConfigureContainer(ContainerBuilder builder)
{
}

public ILifetimeScope AutofacContainer { get; private set; }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
this.AutofacContainer = app.ApplicationServices.GetAutofacRoot();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}

一般注册服务

ConfigureContainer方法中进行服务注册,然后在Configure方法中获取服务实现对象,调用服务的ShowCode方法,具体代码如下:

1
2
3
4
5
6
7
8
9
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<MyService>().As<IMyService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var serviceNoName = this.AutofacContainer.Resolve<IMyService>();
serviceNoName.ShowCode();
}

Autofac注册服务与ASP.NET Core写法相反,先注册实现类,然后再标记这个实现类为哪种类型

运行项目会看到控制台打印了MyService对象调用ShowCode方法时候打印的信息,类似信息如下:

1
MyService.ShowCode:16336406

基于名字注册服务

注释掉上一步的测试代码,一样是在ConfigureContainer方法中进行服务注册,然后在Configure方法中获取服务实现对象,调用服务的ShowCode方法,具体代码如下:

1
2
3
4
5
6
7
8
9
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<MyServiceV2>().Named<IMyService>("service2");
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var service = this.AutofacContainer.ResolveNamed<IMyService>("service2");
service.ShowCode();
}

运行项目会看到控制台打印了MyServiceV2对象调用ShowCode方法时候打印的信息,类似信息如下:

1
MyServiceV2.ShowCode:16336406,MyNameService是否为空:True

属性注入

注释掉上一步的测试代码,在ConfigureContainer方法中进行服务注册,注意需要先将属性的服务先进行注册,再进行调用方的服务注册,然后一样再Configure中获取对象,调用ShowCode方法

1
2
3
4
5
6
7
8
9
10
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<MyNameService>();
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var service = this.AutofacContainer.Resolve<IMyService>();
service.ShowCode();
}

运行项目会看到控制台打印了MyServiceV2对象调用ShowCode方法时候打印的信息,类似信息如下:

1
MyServiceV2.ShowCode:10309404,MyNameService是否为空:False

可以发现MyNameService属性已经不为空了,通过属性注入的操作注入到了服务对象中去,打断点进行调试,可以看出MyNameService类型就是上面注册的类型

AOP切面拦截器

注释掉上一步的测试代码,先在ConfigureContainer方法中注册拦截器,然后在服务,并指定拦截器为刚刚所注册的拦截器,并且允许接口拦截器生效,获取服务与上一步操作一致

1
2
3
4
5
6
7
8
9
10
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<MyInterceptor>();
builder.RegisterType<MyServiceV2>().As<IMyService>().PropertiesAutowired().InterceptedBy(typeof(MyInterceptor)).EnableInterfaceInterceptors();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
var service = this.AutofacContainer.Resolve<IMyService>();
service.ShowCode();
}

运行项目,可以看到控制台在打印出MyServiceV2ShowCode方法所打印的信息前后,有拦截器打印出来的信息,类似信息如下:

1
2
3
Intercept before,Method:ShowCode
MyServiceV2.ShowCode:25116876,MyNameService是否为空:True
Intercept after,Method:ShowCode

创建子容器

子容器主要适用于将服务注册进指定名字的容器里,这样只有在创建出指定名字的容器才可获取到服务对象,其他容器无法获得该服务对象,具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<MyNameService>().InstancePerMatchingLifetimeScope("myScope");
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
using (var myScope = this.AutofacContainer.BeginLifetimeScope("myScope"))
{
var service0 = myScope.Resolve<MyNameService>();
using (var scope = myScope.BeginLifetimeScope())
{
var service1 = scope.Resolve<MyNameService>();
var service2 = scope.Resolve<MyNameService>();

Console.WriteLine($"service0=service1:{service0==service1}");
Console.WriteLine($"service1=service2:{service1==service2}");
}
}
}

运行代码可看到对象获取成功,并且获取到的对象在作用域内为同一个对象,类似信息如下:

1
2
service0=service1:True
service1=service2:True

如果这时候不通过创建指定名字的容器来获得服务对象,会发现代码运行直接报错