ASP.NET Core – Dependency Injection

前言

很久很久以前就写过了 Asp.net core 学习笔记 ( DI 依赖注入 ), 这篇只是整理一下而已.

 

参考

Using dependency injection in a .Net Core console application

 

介绍

Dependency Injection 是一种管理依赖的方式。

class 之间经常会有依赖关系,比如

public class ServiceA 
{
    public void DoSomething() => Console.WriteLine("do something...");
}

public class ServiceB(ServiceA serviceA) 
{
    public void DoSomething() => serviceA.DoSomething();
}

ServiceB 的某个方法需要使用到 ServiceA 的某个方法,这就表示 ServiceB 依赖了 ServerA。

依赖关系会让实例化变得复杂和繁琐。

var serviceA = new ServiceA();
var serviceB = new ServiceB(serviceA);
serviceB.DoSomething();

若想实例化 ServiceB 首先需要实例化 ServiceA,试想想如果依赖链很长的话,代码将变得非常长和混乱。

而依赖注入就是为了解决这个问题而诞生的。使用依赖注入后,我们在实例化时就不需要去理会复杂的依赖关系了。

许多地方都有 Dependency Injection 这个概念。比如 Java、Angular 也有。但是它们的实现都有一些些区别,也可以理解为变种吧,但中心思想是一样的。

 

How It Work ?

首先, 把整个项目的 Service (也就是 class) 都交给一个 ServiceProvider 来管理. 不管你要什么 Service 都必须跟 ServiceProvider 拿.

每个 Service 通过 Construtor 来表达依赖关系.每当有人需要 Service 的时候, ServiceProvider 就会查找 Service 的依赖, 一层层的去创建出所有需要的对象.

整个过程大致上就是这样. 

 

Get Started

用 Console 测试就可以了

dotnet new console -o DependencyInjection
dotnet add package Microsoft.Extensions.DependencyInjection

Console 没有很多 build-in 的 dll, 如果想方便可以一次添加.

dotnet add package Microsoft.AspNetCore.App

 

ServiceCollection & ServiceProvider

ServiceA 依赖 ServiceB

public class ServiceA 
{
    private readonly ServiceB _serviceB;

    public ServiceA(
        ServiceB serviceB
    ) 
    {
        _serviceB = serviceB;
    }

    public string GetValue() {
        return _serviceB.GetValue();
    }
}

public class ServiceB 
{
    public string GetValue() 
    {
        return "Service B value";
    }
}

program.cs

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection(); // 一个 Services 大合集
        serviceCollection.AddScoped<ServiceA, ServiceA>(); // 添加 ServiceA 和 ServiceB 进去大合集
        serviceCollection.AddScoped<ServiceB, ServiceB>();
        var serviceProvider = serviceCollection.BuildServiceProvider(); // 把所有 Service 丢进去后就可以创建出 Provider
        var serviceA = serviceProvider.GetRequiredService<ServiceA>(); // 通过 Provider 获取 Service, Provider 在创建 ServiceA 前, 会先创建 ServiceB 并提供给 ServiceA
        Console.WriteLine(serviceA.GetValue());
    }
}

创建大合集 > 把所有 service 放入合集 > 创建 Provider > 通过 Provider 获取 Service.

Provider 的职责就是管理好依赖, 依据每个 class 的依赖去创建对象. 

 

添加 Service 的细节 Singleton, Scoped, Transient

serviceCollection.AddSingleton<ServiceB, ServiceB>();
serviceCollection.AddScoped<ServiceB, ServiceB>();
serviceCollection.AddTransient<ServiceB, ServiceB>();

Singleton 就是单列, ServiceProvider 第一次创建对象后会把对象收起来, 往后都直接返回这个对象.

Scoped 也是单列, 但是它有一个范围. Singleton 的范围是整个 Application 结束都是单列 (用在 Web Application, 一个 request 就表示一个 Scope)

public static void Main(string[] args)
{
    var serviceCollection = new ServiceCollection(); 
    serviceCollection.AddScoped<ServiceA, ServiceA>();
    serviceCollection.AddScoped<ServiceB, ServiceB>();
    var serviceProvider = serviceCollection.BuildServiceProvider();
    ServiceA serviceA;
    using (var scope = serviceProvider.CreateScope()) // 开启 scope 
    {
        serviceA = scope.ServiceProvider.GetRequiredService<ServiceA>();
        var serviceAA = scope.ServiceProvider.GetRequiredService<ServiceA>();
        Console.WriteLine((serviceA == serviceAA).ToString()); // 在 scope 内是同一个对象
    }
    var serviceAAA = serviceProvider.GetRequiredService<ServiceA>();
    Console.WriteLine((serviceA == serviceAAA).ToString()); // scope 之外和 scope 内是不同的对象
}

Transient 表示每一次 ServiceProvider 都会创建新的实例.

什么时候应该使用什么呢? 参考: Stack Overflow – AddTransient, AddScoped and AddSingleton Services Differences

 

Interface Best Practice

上面的例子中, 直接提供了类型, 这个是不理想的, 我们应该要面向接口编程 (方便扩展, 维护)

serviceCollection.AddScoped<ServiceA, ServiceA>();

设计一个接口

然后使用接口声明

serviceCollection.AddScoped<IGetValue, ServiceA>();
serviceProvider.GetRequiredService<IGetValue>();

这样的好处就是, 以后可以替换具体实现. 只要实现了接口, 任何一个 class 都可以替换上去.

 

Multiple Provide

当同一个接口提供多个 Service 时, 可以通过 GetServices 获取到所有 services, identity 的 UserValidator 就用了这个方式

serviceCollection.AddScoped<IGetValue, ServiceC>();
serviceCollection.AddScoped<IGetValue, ServiceD>();
Console.WriteLine(string.Join(',', services.Select(s => s.GetType().Name))); // ServiceC,ServiceD

 

Create Instance with ServiceProvider

如果我们想通过反射实例化一个带有依赖的 class, 可以这么写

var instance = ActivatorUtilities.CreateInstance<AbcService>(serviceProvider);

把 ServiceProvider 丢进去就可以了. 内部会调用 GetRequiredService

 

Web Application 中使用 Dependency Injection

program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddScoped<ServiceA, ServiceA>();

Razor Page or Controller

public class IndexModel : PageModel
{
    public IndexModel(
        ServiceA serviceA // 可以通过 Constructor
    )
    {
    }

    public void OnGet(ServiceA serviceA) // 可以通过 Parameter 
    //public void OnGet([FromServices] ServiceA serviceA) // 在 .NET 7 之前需要加上 [FromServices]
    {

    }
}

View

@page
@model IndexModel
@inject ServiceA ServiceA // 使用 @inject

 

posted @ 2022-09-30 18:52  兴杰  阅读(123)  评论(0编辑  收藏  举报