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}扩展方法来注册该服务所需的所有服务。

以下例子是使用扩展方法AddDbContextAddIdentity向容器添加服务。

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 依赖注入的更多相关文章

  1. [ASP.NET Core开发实战]基础篇03 中间件

    什么是中间件 中间件是一种装配到应用管道,以处理请求和响应的组件.每个中间件: 选择是否将请求传递到管道中的下一个中间件. 可在管道中的下一个中间件前后执行. ASP.NET Core请求管道包含一系 ...

  2. [ASP.NET Core开发实战]基础篇01 Startup

    Startup,顾名思义,就是启动类,用于配置ASP.NET Core应用的服务和请求管道. Startup有两个主要作用: 通过ConfigureServices方法配置应用的服务.服务是一个提供应 ...

  3. [ASP.NET Core开发实战]基础篇06 配置

    配置,是应用程序很重要的组成部分,常常用于提供信息,像第三方应用登录钥匙.上传格式与大小限制等等. ASP.NET Core提供一系列配置提供程序读取配置文件或配置项信息. ASP.NET Core项 ...

  4. [ASP.NET Core开发实战]基础篇05 服务器

    什么是服务器 服务器指ASP.NET Core应用运行在操作系统上的载体,也叫Web服务器. Web服务器实现侦听HTTP请求,并以构建HttpContext的对象发送给ASP.NET Core应用. ...

  5. [ASP.NET Core开发实战]基础篇04 主机

    主机定义 主机是封闭应用资源的对象. 设置主机 主机通常由 Program 类中的代码配置.生成和运行. HTTP项目(ASP.NET Core项目)创建泛型主机: public class Prog ...

  6. ASP.NET Core 2.2 基础知识(一) 依赖注入

    依赖: 类A用到了类B,我们就说类A依赖类B.如果一个类没有任何地方使用到,那这个类基本上可以删掉了. public class Test { private MyDependency md = ne ...

  7. [ASP.NET Core开发实战]开篇词

    前言 本系列课程文章主要是学习官方文档,再输出自己学习心得,希望对你有所帮助. 课程大纲 本系列课程主要分为三个部分:基础篇.实战篇和部署篇. 希望通过本系列课程,能让大家初步掌握使用ASP.NET ...

  8. 2月送书福利:ASP.NET Core开发实战

    大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...

  9. Asp.Net Core 进阶(三)—— IServiceCollection依赖注入容器和使用Autofac替换它

    Asp.Net Core 提供了默认的依赖注入容器 IServiceCollection,它是一个轻量级的依赖注入容器,所以功能不多,只是提供了基础的一些功能,要实现AOP就有点麻烦,因此在实际工作当 ...

随机推荐

  1. 打印java系统的信息

    System.getProperties() 确定当前系统属性 Properties.list() 将获取到的信息打印到流中 public class Userone { public static ...

  2. 文件权限和访问控制列表ACL (1)

    背景知识: 文件的权限主要针对三类对象进行定义 Owner: 属主u Group: 属组g Other: 其他o 每个文件针对每一类的访问者都设定了三种权限 r: Readable 读 w: Writ ...

  3. 关键字Run Keyword If 如何写多个条件语句、如何在一个条件下执行多个关键字

    Run Keyword If 关键字给出的示例是: 但是,这往往不能满足我们实际需要,比如,我们需要同时判断多个条件是否成立,或者在条件成立时我们想要执行多个关键字,虽然可以进行封装再调用,但是比较麻 ...

  4. 微信小程序--家庭记账小账本(三)

    家庭记账小账本打算先通过微信小程序来实现,昨天就去注册了解了一下微信小程序,感觉比较复杂而且困难.如何将ecplise源代码与小程序连接,如何建立数据库等等都困扰了我.查阅网上的资料也没有很大的进展. ...

  5. 记一次LayUI中Table动态添加列数据

    这次在开发中遇到,有列数不固定的情况.废话不多说,先上图,在上代码. 下面上JS代码 function SearchData() { var dYear = $("#DYear") ...

  6. 一道 3 行代码的 Python面试题,我懵逼了一天

    有意思的题目 题目:写出下面程序运行结果 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的 ...

  7. VisualSVN Server修改默认端口号 443->8443

  8. 微信小程序 progress 进度条 内部圆角及内部条渐变色

    微信小程序progress进度条内部圆角及渐变色 <view class="progress-box"> <progress percent="80&q ...

  9. python爬虫:XPath语法和使用示例

    python爬虫:XPath语法和使用示例 XPath(XML Path Language)是一门在XML文档中查找信息的语言,可以用来在XML文档中对元素和属性进行遍历. 选取节点 XPath使用路 ...

  10. Android SharedPreferences存储详解

    什么是SharedPreferences存储 一种轻量级的数据保存方式 类似于我们常用的ini文件,用来保存应用程序的一些属性设置.较简单的参数设置. 保存现场:保存用户所作的修改或者自定义参数设定, ...