ASP.NET Core使用编译时依赖关系注入(DI)

前言

依赖关系注入(DI),是一种在类及其依赖项之间实现控制反转(IoC)的技术。在ASP.NET Core中,依赖关系注入是“一等公民”,被大量使用。

通常,使用接口作为依赖关系实现抽象化,并且在服务容器中注册依赖关系,最后在运行时由框架负责创建依赖关系的实例,注入到使用它的类的构造函数中。.

但是,如果忘记在服务容器中注册依赖关系,例如下面的代码,IRepository是WeatherForecastController的依赖项,但是没有被注册:

private readonly IRepository repository;

public WeatherForecastController(IRepository repository)
{ 
    this.repository = repository;
}

代码可以正常编译,但是在运行时会报错:

ASP.NET Core使用编译时依赖关系注入(DI)

这就相当于一个隐藏炸弹,你不知道什么时候会爆炸。

编译时依赖关系注入

这个问题可以使用编译时依赖关系注入解决,伪代码如下:

public WeatherForecastController Resolve()
{
    var repository = new RepositoryImpl();

    return new WeatherForecastController((IRepository)repository);
} 

通过直接写出生成类实例的代码,一旦没有生成依赖关系的实例,或者依赖关系的实例类型不对,编译时就会报错,我们可以立刻觉察到问题。

但是,全手写这样的代码也不现实,而且一旦修改依赖关系,代码就必须大量重构。

有不有更简单的方案?

StrongInject

StrongInject是一个用于实现编译时依赖关系注入的类库,如果你要解析的类型未注册,则会在编译时而不是运行时报错。

下面,我们用一个例子来演示如何使用StrongInject。

1.引用Nuget包

创建ASP.NET Core Web API项目,然后引用如下Nuget包:

StrongInject.Extensions.DependencyInjection

2.注册Controller

修改Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    //services.AddControllers();
    services.AddControllers().ResolveControllersThroughServiceProvider();

    services.AddTransientServiceUsingContainer<Container, WeatherForecastController>();
}

告诉ASP.NET Core使用Container类解析Controller的依赖关系。

3.依赖关系注入

新建Container.cs:

[Register(typeof(WeatherForecastController), Scope.InstancePerResolution)]
[Register(typeof(DemoRepository), Scope.SingleInstance, typeof(IRepository))]
public partial class Container : IContainer<WeatherForecastController>
{
}

注册IRepository的实现为DemoRepository,其中Scope是生成实例的方式:

  • InstancePerResolution:单个实例在为单个Controller创建的所有依赖项之间共享。例如,如果A依赖B和C,而B和C依赖于D的实例,那么当A被解析时,B和C将共享相同的D实例。
  • InstancePerDependency:每次使用都会创建一个新实例。例如,类型B在A的构造函数中定义了两次,那么两个不同的实例将被传入构造函数。
  • SingleInstance:一个实例将在所有Controller的所有依赖项之间共享。

4.编译

编译代码,可以看到StrongInject是基于Source Generators实现,为WeatherForecastController生成了依赖注入解析代码:

ASP.NET Core使用编译时依赖关系注入(DI)

如果我们在WeatherForecastController中增加一个依赖:

public WeatherForecastController(IRepository repository, ILogger<WeatherForecastController> logger)

由于依赖没有注册,编译时报错:

ASP.NET Core使用编译时依赖关系注入(DI)

结论

除了实现了编译时依赖关系注入功能,由于StrongInject基于Source Generators实现,没有字典查找,也没有运行时代码生成,因此依赖关系解析速度相对于运行时注入应该要快很多。