core控制器属性注入的用处_ASP.NET Core依赖注入的那些事

依赖注入 (DI)

定义

DI—Dependency Injection,即 "依赖注入", 它是一种在类及其依赖关系之间实现控制反转 (IoC) 的技术, 注意, 它是具体的技术, 而非是设计思想.

为什么需要它

依赖注入是控制反转的具体实现, 那么控制反转又是什么? 这是一个和反转对应的 "正转" 例子

public class MyDependency

{

public MyDependency()

{

}

public Task WriteMessage(string message)

{

Console.WriteLine(

$"MyDependency.WriteMessage called. Message: {message}");

return Task.FromResult(0);

}

}

public class IndexModel : PageModel

{

MyDependency _dependency = new MyDependency();

public async Task OnGetAsync()

{

await _dependency.WriteMessage(

"IndexModel.OnGetAsync created this message.");

}

}

这个例子中,IndexModel 需要自己来实例化 MyDependency 类, 然后再来调用它的 WriteMessage 方法, 但是如果这样做, 可能会存在如下问题:要用不同的实现替换 MyDependency,必须修改类。

如果 MyDependency 具有依赖关系,则必须由类对其进行配置。 在具有多个依赖于 MyDependency 的类的大型项目中,配置代码在整个应用中会变得分散。

这种实现很难进行单元测试。 应用应使用模拟或存根 MyDependency 类,该类不能使用此方法。

如何使用

依赖注入是怎么解决这些问题的:使用接口或基类抽象化依赖关系实现。

注册服务容器中的依赖关系。 ASP.NET Core 提供了一个内置的服务容器 IServiceProvider。 服务已在应用的 Startup.ConfigureServices 方法中注册。

将服务注入 到使用它的类的构造函数中。 框架负责创建依赖关系的实例,并在不再需要时对其进行处理。

通俗点来说, 本来由你的类来创建实例, 现在由我框架来给你提供, 你只需要注册你需要的接口实现或者基类. 这就是控制反转.

上面的代码改成依赖注入的形式:public interface IMyDependency

{

Task WriteMessage(string message);

}

public class MyDependency : IMyDependency

{

public Task WriteMessage(string message)

{

Console.Writeline(

"MyDependency.WriteMessage called. Message: {MESSAGE}",message);

return Task.FromResult(0);

}

}

public class IndexModel : PageModel

{

private readonly IMyDependency _myDependency;

public IndexModel(

IMyDependency myDependency

{

_myDependency = myDependency;

}

public async Task OnGetAsync()

{

await _myDependency.WriteMessage(

"IndexModel.OnGetAsync created this message.");

}

}

一般情况下,Startup.CS 下来注册这些服务, 常规服务我们使用 AddSingleton、AddScoped、AddTransient 这三个方法来注册服务, 但是框架同时也提供了一些内置服务集合, 当服务集合扩展方法可用于注册服务, 用得最常见的, 可能就是 AddDbContext 了, 比如注册一个 DbContext 的代码如下public void ConfigureServices(IServiceCollection services)

{

services.AddDbContext(options =>

options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

}

服务生命周期

在上面提到了注册常规服务一般会用到 AddSingleton、AddScoped、AddTransient 三个方法, 那么他们有什么异同? 异同就是它们注册出来的服务的生命周期不同, 分为暂时

暂时生存期服务 (AddTransient) 是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。

范围内

作用域生存期服务 (AddScoped) 以每个客户端请求(连接)一次的方式创建。警告

在中间件内使用有作用域的服务时,请将该服务注入至 Invoke 或 InvokeAsync 方法。 请不要通过构造函数注入进行注入,因为它会强制服务的行为与单一实例类似。 有关详细信息,请参阅 写入自定义 ASP.NET Core 中间件。

单例

单一实例生存期服务 (AddSingleton) 是在第一次请求时(或者在运行 Startup.ConfigureServices 并且使用服务注册指定实例时)创建的。 每个后续请求都使用相同的实例。 如果应用需要单一实例行为,建议允许服务容器管理服务的生存期。 不要实现单一实例设计模式并提供用户代码来管理对象在类中的生存期。警告

从单一实例解析有作用域的服务很危险。 当处理后续请求时,它可能会导致服务处于不正确的状态。

三种不同生命周期很好理解, 但是值得注意的是, 在实际使用中, 依赖项的生命周期不能大于被依赖项, 比如上面我提到的 AddDbContext, 它将 TDbContext 的生命周期注册为范围内 (Scoped), 那么注入了 TDbContext 的类就必须注册为范围内(Scoped) 或暂时(Transitient), 否则会报如下错误System.AggregateException: "Some services are not able to be constructed (Error while validating the service descriptor "*** Lifetime: Singleton ImplementationType: ***": Cannot consume scoped service "***" from singleton

同时, 有一个 TryAdd, 这个是只有当同类服务没有被注册的情况下才会去注册services.AddSingleton();

// The following line has no effect:

services.TryAddSingleton();

如上面这个例子中, 因为 IMyDependency 已经被 MyDependency 注册, 所以 DifferentDependcy 不会被注册.(TryAdd 的生命周期是自选的, 当然也可以直接使用诸如 TryAddScoped 之类)

构造函数注入行为

服务可以通过两种机制来解析:IServiceProvider

ActivatorUtilities – 允许在依赖关系注入容器中创建没有服务注册的对象。 ActivatorUtilities 用于面向用户的抽象,例如标记帮助器、MVC 控制器和模型绑定器。

构造函数可以接受依赖关系注入不提供的参数,但参数必须分配默认值。

当服务由 IServiceProvider 或 ActivatorUtilities 解析时,构造函数注入需要 public 构造函数 。

当服务由 ActivatorUtilities 解析时,构造函数注入要求只存在一个适用的构造函数。 支持构造函数重载,但其参数可以全部通过依赖注入来实现的重载只能存在一个。

ActivatorUtilities 是通过构建 ExpressionTree 的方式对类型的构造器进行构造并创建出对象的, 并使用 IServiceProvider 注入的构造器;而 ServiceProvider 则是完全通过依赖注入的生命周期的 CallSite,对类型进行递归创建对象的。由于 IServiceProvider 是接口, 比 ActivatorUtilities 的扩展性要好, 因此运用得比较多.

PS

使用依赖注入的方式意味着放弃直接使用基本类型做参数 (当然不排除奇技淫巧的方式), 如果构造函数需要用到基本类型怎么办? 官方推荐的是使用配置(IConfiguration) 和选项模式,IConfiguration 就是在 Appsetting.Json 中写相关类型, 选项模式的话也是通过将 Option 注入到 Config 中的形式实现的.

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值