ASP.NET Core中的依赖注入【上】
此为系列文章,对MSDN ASP.NET Core 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解
ASP.NET Core支持DI软件设计模式,其是一种为了在类及其依赖对象之间实现控制反转(IoC)的一项技术。获取更多特定于MVC控制器的依赖注入的信息,可以参考Dependency injection into controllers in ASP.NET Core。
依赖注入概述
任何其他对象需要的一个对象都可以称之为依赖。检查如下具有一个WriteMessage方法的MyDependency类,app中的其他类会依赖它:
public class MyDependency
{
public MyDependency()
{
} public Task WriteMessage(string message)
{
Console.WriteLine(
$"MyDependency.WriteMessage called. Message: {message}"); return Task.FromResult();
}
}
我们可以创建一个MyDependency类的实例来使得WriteMessage方法对于一个类是可用的。如下所示,MyDependency类便是IndexModel类的一个依赖:
public class IndexModel : PageModel
{
MyDependency _dependency = new MyDependency(); public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
这个类创建并直接依赖于MyDependency类的实例。代码依赖(正如上述代码)是有问题的,基于如下理由,我们应该避免它:
- 如果要用一个不同的实现来替换
MyDependency,使用了MyDependency的所有类必须要进行改动。 - 如果
MyDependency类也具有依赖,那么MyDependency类的使用类必须对它们进行配置,会使得配置代码散布在整个app中。 - 这种实现难以进行单元测试,app应该使用一个模拟的或者微小的
MyDependency类来进行单元测试,但在这种模式下这是不可能的。
依赖注入通过以下方式解决这些问题:
- 使用了接口或基类来对依赖的实现进行抽象。
- 在一个服务容器中对依赖进行注册。ASP.NET Core提供了一个内置的服务容器,IServiceProvider。服务在
Startup.ConfigureServices方法中进行注册。 - 将服务注入到使用它们的类的构造函数中。框架负责创建一个依赖的实例,并在不再需要它的时候将其销毁。
在示例代码中,IMyDependency接口定义了服务提供给app的方法:
public interface IMyDependency
{
Task WriteMessage(string message);
}
一个具体类型MyDependency实现了这个接口:
public class MyDependency : IMyDependency
{
private readonly ILogger<MyDependency> _logger; public MyDependency(ILogger<MyDependency> logger)
{
_logger = logger;
} public Task WriteMessage(string message)
{
_logger.LogInformation(
"MyDependency.WriteMessage called. Message: {MESSAGE}",
message); return Task.FromResult();
}
}
MyDependency在其构造函数中 请求了一个ILogger<TCategoryName>。以链接的方式来使用依赖注入并不少见。每一个被请求的依赖都会依次请求它们自己的依赖。容器会对图形化的依赖进行解析并最终返回一个完整的解析后的服务。这一系列需要解析的服务被称为依赖树,或者依赖图,对象图。
IMyDependency以及ILogger<TCategoryName>必须在服务容器中进行注册。IMyDependency在Startup.ConfigureServices中进行注册;而日志抽象架构负责ILogger<TCategoryName>的注册。所以它是一个框架提供的服务,其被框架默认注册。
容器通过使用(generic) open types来对ILogger<TCategoryName>进行解析,而不需要对每一个泛型构造类型进行注册:
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
在示例代码中,使用具体类型MyDependency来对IMyDependency服务进行注册。这个注册将服务的生命周期限定为一个请求的生命周期,服务的生命周期会在后续进行讨论:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(); services.AddScoped<IMyDependency, MyDependency>();
services.AddTransient<IOperationTransient, Operation>();
services.AddScoped<IOperationScoped, Operation>();
services.AddSingleton<IOperationSingleton, Operation>();
services.AddSingleton<IOperationSingletonInstance>(new Operation(Guid.Empty)); // OperationService depends on each of the other Operation types.
services.AddTransient<OperationService, OperationService>();
}
注意,每一个services.Add{SERVICE_NAME}扩展方法都会添加(并潜在配置)了服务。比如services.AddMvc()添加了Razor视图和MVC支持。我们建议app遵从此约定,将扩展方法放置在此命名空间Microsoft.Extensions.DependencyInjection以对服务注册的分组进行封装。
如果服务的构造函数需要一个内置类型,比如String,那么可以使用配置或者选项模式进行注入:
public class MyDependency : IMyDependency
{
public MyDependency(IConfiguration config)
{
var myStringValue = config["MyStringKey"]; // Use myStringValue
} ...
}
服务实例被使用它的类的构造函数所请求并被分配给一个私有字段。在整个类中,便可以使用这个字段来访问服务。
在示例代码中,IMyDependency的实例会被请求并用来调用服务的WriteMessage方法。
public class IndexModel : PageModel
{
private readonly IMyDependency _myDependency; public IndexModel(
IMyDependency myDependency,
OperationService operationService,
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance singletonInstanceOperation)
{
_myDependency = myDependency;
OperationService = operationService;
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = singletonInstanceOperation;
} public OperationService OperationService { get; }
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; } public async Task OnGetAsync()
{
await _myDependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
注入到Startup中的服务
当使用泛型宿主(IHostBuilder)时,只有如下的服务类型可以被注入到Startup的构造函数中:
IWebHostEnvironment- IHostEnvironment
- IConfiguration
服务可以被注入到Startup.Configure方法:
public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
{
...
}
更多信息,请参考App startup in ASP.NET Core。
框架提供的服务
Startup.ConfigureServices方法负责定义app使用的服务,包括一些平台级特性,比如Entity Framework Core和ASP.NET Core MVC。根据how the host was configured,提供给ConfigureServices的初始IServiceCollection具有一些由框架定义的服务。对于一个基于ASP.NET Core模板的app来说,具有几百个框架定义的服务并不少见。下面的表格列出了一小部分框架定义的服务:
| 服务类型 | 生命周期 |
|---|---|
| Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory | Transient |
IHostApplicationLifetime |
Singleton |
IWebHostEnvironment |
Singleton |
| Microsoft.AspNetCore.Hosting.IStartup | Singleton |
| Microsoft.AspNetCore.Hosting.IStartupFilter | Transient |
| Microsoft.AspNetCore.Hosting.Server.IServer | Singleton |
| Microsoft.AspNetCore.Http.IHttpContextFactory | Transient |
| Microsoft.Extensions.Logging.ILogger<TCategoryName> | Singleton |
| Microsoft.Extensions.Logging.ILoggerFactory | Singleton |
| Microsoft.Extensions.ObjectPool.ObjectPoolProvider | Singleton |
| Microsoft.Extensions.Options.IConfigureOptions<TOptions> | Transient |
| Microsoft.Extensions.Options.IOptions<TOptions> | Singleton |
| System.Diagnostics.DiagnosticSource | Singleton |
| System.Diagnostics.DiagnosticListener | Singleton |
使用扩展方法注册额外的服务
当一个服务集合扩展方法可用来注册一个服务(以及它所依赖的服务)时,按照惯例是使用一个单独的Add{SERVICE_NAME}扩展方法来注册这个服务所需要的所有服务。下面的代码演示了如何使用扩展方法AddDbContext<TContext> 和AddIdentityCore:来给容器添加额外的服务:
public void ConfigureServices(IServiceCollection services)
{
... services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); ...
}
更多信息,请参考API文档中的ServiceCollection类。
服务生命周期
为每一个注册的服务选择一个合适的生命周期。ASP.NET Core服务可被配置为如下的生命周期:
Transient
每一次服务被从服务容器中请求都会创建一个Transient生命周期服务(AddTransient)。这种生命周期对于轻量级,无状态的服务来说,工作得最好。
Scoped
每一次的客户端请求都会创建一个Scoped生命周期服务。
注意,当在中间件中使用一个scoped服务时,将服务注入到Invoke或者InvokeAsync方法。不要通过构造函数注入的方式进行注入,因为其迫使服务表现的像一个singleton,更多信息,请参考Write custom ASP.NET Core middleware。
Singleton
当Singleton生命周期服务第一次被请求时(当Startup.ConfigureServices开始运行,并且服务的一个实例被服务容器所指定),它们便会被创建。每一个后续的请求都会使用同一个实例。如果app需要单例行为,我们推荐使用服务容器来管理服务生命周期。不用实现单例模式并提供用户代码在类中管理服务对象的生命周期。
警告:从一个单例中解析scoped服务是很危险的。它会导致服务在处理后续的请求时具有不正确的状态。
ASP.NET Core中的依赖注入【上】的更多相关文章
- ASP.NET Core中的依赖注入(1):控制反转(IoC)
ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了"标准化&qu ...
- ASP.NET Core中的依赖注入(2):依赖注入(DI)
IoC主要体现了这样一种设计思想:通过将一组通用流程的控制从应用转移到框架之中以实现对流程的复用,同时采用"好莱坞原则"是应用程序以被动的方式实现对流程的定制.我们可以采用若干设计 ...
- ASP.NET Core中的依赖注入(4): 构造函数的选择与服务生命周期管理
ServiceProvider最终提供的服务实例都是根据对应的ServiceDescriptor创建的,对于一个具体的ServiceDescriptor对象来说,如果它的ImplementationI ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【总体设计 】
本系列前面的文章我们主要以编程的角度对ASP.NET Core的依赖注入系统进行了详细的介绍,如果读者朋友们对这些内容具有深刻的理解,我相信你们已经可以正确是使用这些与依赖注入相关的API了.如果你还 ...
- ASP.NET Core中的依赖注入(5): ServiceProvider实现揭秘 【解读ServiceCallSite 】
通过上一篇的介绍我们应该对实现在ServiceProvider的总体设计有了一个大致的了解,但是我们刻意回避一个重要的话题,即服务实例最终究竟是采用何种方式提供出来的.ServiceProvider最 ...
- ASP.NET Core 中的 依赖注入介绍
ASP.NET Core 依赖注入 HomeController public class HomeController : Controller { private IStudentReposito ...
- ASP.NET Core中的依赖注入(3): 服务的注册与提供
在采用了依赖注入的应用中,我们总是直接利用DI容器直接获取所需的服务实例,换句话说,DI容器起到了一个服务提供者的角色,它能够根据我们提供的服务描述信息提供一个可用的服务对象.ASP.NET Core ...
- ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】
到目前为止,我们定义的ServiceProvider已经实现了基本的服务提供和回收功能,但是依然漏掉了一些必需的细节特性.这些特性包括如何针对IServiceProvider接口提供一个Service ...
- ASP.NET Core 中的依赖注入
目录 什么是依赖注入 ASP .NET Core 中使用依赖注入 注册 使用 释放 替换为其它的 Ioc 容器 参考 什么是依赖注入 软件设计原则中有一个依赖倒置原则(DIP),为了更好的解耦,讲究要 ...
- ASP.NET Core 中的依赖注入 [共7篇]
一.控制反转(IoC) ASP.NET Core在启动以及后续针对每个请求的处理过程中的各个环节都需要相应的组件提供相应的服务,为了方便对这些组件进行定制,ASP.NET通过定义接口的方式对它们进行了 ...
随机推荐
- 简单实现一个Unity3d的Timer
数量使用的不太多,没有实现对象池. using System.Collections; using System.Collections.Generic; using UnityEngine; usi ...
- Python - python3.7新增的contextvars vs Thread local(threading.local)
总结 和threading.local()类似.Python3.7新增. thread.local(): 不同线程,同一个变量保存不同的值. contextvars: 不同上下文,同一个变量保存不同的 ...
- 虚拟机linux系统网络连接配置问题总结
1.虚拟机与CentOS的安装与配置参考本人博客:https://www.cnblogs.com/ClikeL/p/11743520.html 2.测试网络连接 ping www.baidu.com ...
- Java-POJ1003-Hangover
题目大意: 给出一个浮点数a,求出使得 不等式 1/2 + 1/3 + ... + 1/(n+1) ≥ a 成立的最小值 大水题,由于数据范围小,给出了确认上界5.20,满足二分答案 但是我懒啊,直接 ...
- mysql 查询时间戳格式化 和thinkphp查询时间戳转换
我在网上看了好多写的,都差不多,甚至好多都是一个人写的被别人转载啥的,哎 我写一个比较简单的 1.mysql语句 格式化时间戳 select id,name,FROM_UNIXTIME(time,'% ...
- 使用ResponseBodyAdvice统一包装响应返回String的时候出现java.lang.ClassCastException: com.xxx.dto.common.ResponseResult cannot be cast to java.lang.String
代码如下: @Override public ResponseResult<Object> beforeBodyWrite(Object returnValue, MethodParame ...
- Android 获取手机的厂商、型号、Android系统版本号等工具类(转载)
Android 获取手机的厂商.型号.Android系统版本号等工具类 1.获取手机制造厂商 2.获取手机型号 3.获取手机系统当前使用的语言 4.获取Android系统版本号 5.获取手机IMEI串 ...
- Sql 中获取年月日时分秒的函数
getdate():获取系统当前时间 dateadd(datepart,number,date):计算在一个时间的基础上增加一个时间后的新时间值,比如:dateadd(yy,30,getdate()) ...
- python中使用graphviz环境配置
去官网下载graphviz,并下一步安装 配置graphviz的bin目录到path环境变量下 python相关包: 使用conda注意:conda install graphviz 可能没用,要使用 ...
- Git - 06. git checkout
1. 概述 已经学会查看 提交记录 尝试在 提交记录 之间做跳转 2. 命令 跳转到提交 概述 跳转到 日志 里的某次提交 命令 # 撤销 工作区的所有改动, 返回 当前分支 的 上一个提交 > ...