[ASP.NET Core开发实战]基础篇02 依赖注入
ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法。
什么是依赖注入
我们看一下下面的例子:
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实例,直接依赖于MyDependency。在开发时避免使用这种方式,原因如下:
- 要替换不同实现的MyDependency时,必须修改类。
- 如果MyDependency也有依赖,必须由类对其进行配置。
- 很难进行单元测试。
可以通过以下方式解决这些问题:
- 使用接口或基类。
- 注册服务窗口中的依赖关注。
- 通过构造函数注入。
将上面的例子改造为依赖注入方式:
public interface IMyDependency
{
Task WriteMessage(string message);
}
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(0);
}
}
public class IndexModel : PageModel
{
MyDependency _dependency;
public IndexModel(MyDependency dependency){
_dependency = dependency;
}
public async Task OnGetAsync()
{
await _dependency.WriteMessage(
"IndexModel.OnGetAsync created this message.");
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IMyDependency, MyDependency>();
}
}
在Startup里注册服务
服务可以在Startup.Configure注册:
public void Configure(IApplicationBuilder app, IOptions<MyOptions> options)
{
...
}
使用扩展方法注册服务
当使用服务集合的扩展方法来注册服务,约定使用Add{SERVICE_NAME}扩展方法来注册该服务所需的所有服务。
以下例子是使用扩展方法AddDbContext和AddIdentity向容器添加服务。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
...
}
服务生命周期
服务的生命周期有三种:Transient、Scoped、Singleton。
Transient
Transient,意为暂时的,是每次服务容器进行请求时创建的,即服务每次实例化时创建一次。这种生存期适合轻量级、无状态的服务。
Scoped
Scoped,意为范围内的,每个客户端请求(连接)时会创建一次。以Web为例,即一次Http请求内创建一次且请求内有效。
Singleton
Singleton,意为单例的,是在第一次请求时创建的。后面每次请求时使用的是同一个实例。在应用关闭,释放ServiceProvider时,会释放。
验证服务生命周期
下面示例是演示服务生命周期的差异。
public interface IOperation
{
Guid OperationId { get; }
}
public interface IOperationTransient : IOperation
{
}
public interface IOperationScoped : IOperation
{
}
public interface IOperationSingleton : IOperation
{
}
public interface IOperationSingletonInstance : IOperation
{
}
public class Operation : IOperationTransient,
IOperationScoped,
IOperationSingleton,
IOperationSingletonInstance
{
public Operation() : this(Guid.NewGuid())
{
}
public Operation(Guid id)
{
OperationId = id;
}
public Guid OperationId { get; private set; }
}
public class OperationService
{
public OperationService(
IOperationTransient transientOperation,
IOperationScoped scopedOperation,
IOperationSingleton singletonOperation,
IOperationSingletonInstance instanceOperation)
{
TransientOperation = transientOperation;
ScopedOperation = scopedOperation;
SingletonOperation = singletonOperation;
SingletonInstanceOperation = instanceOperation;
}
public IOperationTransient TransientOperation { get; }
public IOperationScoped ScopedOperation { get; }
public IOperationSingleton SingletonOperation { get; }
public IOperationSingletonInstance SingletonInstanceOperation { get; }
}
在Startup.ConfigureServices中,指定各服务的生命周期。
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>();
}
示例IndexModel:
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.");
}
}
以下是两次访问IndexModel的结果:
第一个请求:
控制器操作:
暂时性:d233e165-f417-469b-a866-1cf1935d2518
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
OperationService 操作:
暂时性:c6b049eb-1318-4e31-90f1-eb2dd849ff64
作用域:5d997e2d-55f5-4a64-8388-51c4e3a1ad19
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
第二个请求:
控制器操作:
暂时性:b63bd538-0a37-4ff1-90ba-081c5138dda0
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
OperationService 操作:
暂时性:c4cbacb8-36a2-436d-81c8-8c1b78808aaf
作用域:31e820c5-4834-4d22-83fc-a60118acb9f4
单一实例:01271bc1-9e31-48e7-8f7c-7261b040ded9
实例:00000000-0000-0000-0000-000000000000
从上面的结果,观察OperationId的变化:
- 暂时性,值始终不同。
- 作用域,同一请求内相同,不同请求不同。
- 单例,不管是同一请求还是不同请求,都一样。
最佳实践
最佳做法:
- 设计服务以使用依赖关系注入来获取其依赖关系。
- 避免有状态的、静态类和成员。将应用设计为改用单一实例服务,可避免创建全局状态。
- 避免在服务中直接实例化依赖类。 直接实例化将代码耦合到特定实现。
- 不在应用类中包含过多内容,确保设计规范,并易于测试。
[ASP.NET Core开发实战]基础篇02 依赖注入的更多相关文章
- [ASP.NET Core开发实战]基础篇03 中间件
什么是中间件 中间件是一种装配到应用管道,以处理请求和响应的组件.每个中间件: 选择是否将请求传递到管道中的下一个中间件. 可在管道中的下一个中间件前后执行. ASP.NET Core请求管道包含一系 ...
- [ASP.NET Core开发实战]基础篇01 Startup
Startup,顾名思义,就是启动类,用于配置ASP.NET Core应用的服务和请求管道. Startup有两个主要作用: 通过ConfigureServices方法配置应用的服务.服务是一个提供应 ...
- [ASP.NET Core开发实战]基础篇06 配置
配置,是应用程序很重要的组成部分,常常用于提供信息,像第三方应用登录钥匙.上传格式与大小限制等等. ASP.NET Core提供一系列配置提供程序读取配置文件或配置项信息. ASP.NET Core项 ...
- [ASP.NET Core开发实战]基础篇05 服务器
什么是服务器 服务器指ASP.NET Core应用运行在操作系统上的载体,也叫Web服务器. Web服务器实现侦听HTTP请求,并以构建HttpContext的对象发送给ASP.NET Core应用. ...
- [ASP.NET Core开发实战]基础篇04 主机
主机定义 主机是封闭应用资源的对象. 设置主机 主机通常由 Program 类中的代码配置.生成和运行. HTTP项目(ASP.NET Core项目)创建泛型主机: public class Prog ...
- ASP.NET Core 2.2 基础知识(一) 依赖注入
依赖: 类A用到了类B,我们就说类A依赖类B.如果一个类没有任何地方使用到,那这个类基本上可以删掉了. public class Test { private MyDependency md = ne ...
- [ASP.NET Core开发实战]开篇词
前言 本系列课程文章主要是学习官方文档,再输出自己学习心得,希望对你有所帮助. 课程大纲 本系列课程主要分为三个部分:基础篇.实战篇和部署篇. 希望通过本系列课程,能让大家初步掌握使用ASP.NET ...
- 2月送书福利:ASP.NET Core开发实战
大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...
- Asp.Net Core 进阶(三)—— IServiceCollection依赖注入容器和使用Autofac替换它
Asp.Net Core 提供了默认的依赖注入容器 IServiceCollection,它是一个轻量级的依赖注入容器,所以功能不多,只是提供了基础的一些功能,要实现AOP就有点麻烦,因此在实际工作当 ...
随机推荐
- Python性能分析与优化PDF高清完整版免费下载|百度云盘
百度云盘|Python性能分析与优化PDF高清完整版免费下载 提取码:ubjt 内容简介 全面掌握Python代码性能分析和优化方法,消除性能瓶颈,迅速改善程序性能! 对于Python程序员来说,仅仅 ...
- C/C++编程笔记:C语言写推箱子小游戏,大一学习C语言练手项目
C语言,作为大多数人的第一门编程语言,重要性不言而喻,很多编程习惯,逻辑方式在此时就已经形成了.这个是我在大一学习 C语言 后写的推箱子小游戏,自己的逻辑能力得到了提升,在这里同大家分享这个推箱子小游 ...
- luogu P4294 [WC2008]游览计划
LINK:游览计划 斯坦纳树例题. 斯坦纳树是这样一类问题:带权无向图上有K个关键点 求出包含这K个点的最小生成树. 也就是说 求最小生成树 但是 并不是整张图 仅限于K个点. 可以发现我们利用克鲁斯 ...
- 算法学习笔记:2-SAT
SAT 是适定性(Satisfiability)问题的简称.一般形式为 k - 适定性问题,简称 k-SAT.而当 \(k>2\) 时该问题为 NP 完全的.所以我们只研究 \(k=2\) 的情 ...
- JavaScript 模块封装
JavaScript 模块封装 前言介绍 在最早的时候JavaScript这门语言其实是并没有模块这一概念,但是随着时间的推移与技术的发展将一些复用性较强的代码封装成模块变成了必要的趋势. 在这篇文章 ...
- C# 使用代理实现线程间调用
实现功能: 后台线程改变窗体控件(flowLayoutPanel1)的状态. 利用 this.flowLayoutPanel1.InvokeRequired == false,可以知道是主线程调用的自 ...
- 代码优化之return 减少括号嵌套
代码优化之return 减少括号嵌套 例如下面的公共方法 // 优化 substring方法 解决边界越界问题 空指针问题 优化前 public static String subString ...
- LeetCode 413 Arithmetic Slices详解
这个开始自己做的动态规划复杂度达到了O(n), 是用的是2维的矩阵来存前面的数据,复杂度太高了, 虽然好理解,但是没效率,后面看这个博客发现没有动态规划做了这个题 也是比较厉害. 转载地址: http ...
- 【IDE】WebStorm 调整Tab缩进为2空格 -- 为遵循ESLint语法规范
步骤一 修改这三处的值为:2 步骤二 把这两处默认的勾选去掉,不让其detection当前文件的Tab缩进 注意! 通过上面两个步骤,我们只是改变了在JS文件的Tab缩进改为2个空格 但是,*.vue ...
- HTML基础-02
<header>网页头部 <hgroup>一个标题组 <main>网页主体 <article> ...