依赖注入在 ASP.NET Core 中起中很重要的作用,也是一种高大上的编程思想,它的总体原则就是:俺要啥,你就给俺送啥过来。

服务类型的实例转由容器自动管理,无需我们在代码中显式处理。

因此,有了依赖注入后,你的编程思维就得变一变了。

在过去,许多功能性的类型(比如一个加密解密的类),我们都喜欢将其定义为静态(static),而有了依赖注入,你就要避免使用静态类型,应该交由服务容器帮你管理,只要你用好了,你会发现依赖注入是很方便的。

依赖注入的初级玩法,也是比较标准的玩法,此种玩法有两种模式:

1、十代单传模式:一个接口对应一个类,比如先定义接口 IA、IB,随后,类A实现 IA,类B 实现 IB。一对一。也可以是抽象类(或基类)E,然后 F 继承 E 类。

2、断子绝孙模式:直接就写一个类,不考虑派生,直接就添加到服务容器中。

来,看个例子。

我先定义个接口。

public interface IPlayGame
{
void Play();
}

然后,写一个类来实现它。

public class NBPlayGame : IPlayGame
{
public void Play()
{
Console.WriteLine("全民打麻药。");
}
}

我们知道,所谓服务类,其实就是普通类,这些类一般用于完成某些功能,比如计算 MD5 值。接着呢,还记得 Startup 类有个 ConfigureServices 方法吧,对,就在这厮里面把我们刚刚那个服务进行注册(就是添加到 ServiceCollection 集合中)。

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IPlayGame, NBPlayGame>();
}

添加的时候很简单,类型一对一,IPlayGame 接口与 NBPlayGame 类对应。添加时有三种方法你可以调用,实际上对应着,服务类在容器中的生命周期。

AddSingleton:单个实例,这是寿命最长的,与天同寿。整个应用程序中仅用一个实例。

AddTransient:这个是最短命的,可能是天天晚上加班熬夜,死得很快。此种情况下,服务类的实例是用的时候创建,用完后直接销毁。

AddScoped:这个比较难理解。它的生命周期在单个请求内,包括客户端与服务器之间随后产生的子请求,反正只要请求的会话结束了,就会清理。

后,你就可以进行注入了,比如在中间件,在控制器,或者在其他服务类的构造函数上(中间件是在 Invoke / InvokeAsync 方法上)进行实例接收。

现在来用一下,写一个中间件。

public class TestMiddleware
{
public TestMiddleware(RequestDelegate next) { } public Task InvokeAsync(HttpContext context, IPlayGame game)
{
game.Play();
return Task.CompletedTask;
}
}

已注册的服务会注入到 InvokeAsync 方法的参数中。注意第一个参数是 HttpContext,这是必须参数,后面的是注入的参数。

最后,在 Startup 类的 Configure 方法中就可以 use 这个中间件了。

public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<TestMiddleware>();
}

运行后,Play 方法调用,在控制台中输出以下结果。

“断子绝孙”模式,不使用接口规范,直接写功能类。

public class DoSomething
{
public string GetMessage() => "你好,刚才 Boss 找你。";
}

注册服务时更简单。

public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<DoSomething>();
}

在 Configure 方法中进行注入。

public void Configure(IApplicationBuilder app, DoSomething thing)
{
Console.WriteLine(thing.GetMessage());
}

运行后,输出结果如下。

在容器中,使用 ServiceDescriptor 类来存储服务类型相关的信息。

其中,ServiceType 表示的是服务的类型,如果服务是有接口与实现类的,那么这个属性指的是接口的类型,实现类的类型信息由 ImplementationType 属性存储。

如果没有接口,直接只定义类型,那么这个类型的信息就存到 ServiceType 属性上,ImplementationType 属性不使用。

上面这些例子中,ServiceType 是 IPlayGame 接口相关信息,ImplementationType 是 NBPlayGame 类的信息。

如果像上面 DoSomething 类的情况,则 ServiceType 为 DoSomething 相关的信息,ImplementationType 为空。

接下来,咱们看高级玩法。

定义一个接口。

public interface IDemoService
{
string Version { get; }
void Run();
}

然后,有两个类实现这个接口。

public class DemoService1 : IDemoService
{
public string Version => "v1";
public void Run()
{
Console.WriteLine("第一个服务实现类。");
}
}
public class DemoService2 : IDemoService
{
public string Version => "v2"; public void Run()
{
Console.WriteLine("第二个服务实现类。");
}
}

然后,我们注册服务。

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IDemoService, DemoService1>();
services.AddTransient<IDemoService, DemoService2>();
}

然后我们照例,接收注入,咱们依旧使用中间件的方法参数接收。

public class DemoMiddleware
{
public DemoMiddleware(RequestDelegate next)
{
// 由于程序约定,此构造函数必须提供。
}
public async Task InvokeAsync(HttpContext context, IDemoService sv)
{
await context.Response.WriteAsync(sv.Version);
}
}

然后,在 Startup.Configure 方法中使用该中间件。

public void Configure(IApplicationBuilder app, DoSomething thing)
{
app.UseMiddleware<DemoMiddleware>();
}

运行之后,你发现问题了,看看输出

参数仅能接收到最后注册的实现类型实例,也就是 DemoService2 类。

所以就看到网上有不少朋友发贴问了,.NET Core 是不是不支持多个服务实现类的注入?这难倒了很多人。

方法一、接收 IServiceProvider 类型的注入。

public async Task InvokeAsync(HttpContext context, IServiceProvider provider)
{
StringBuilder sb = new StringBuilder();
foreach (var sv in provider.GetServices<IDemoService>())
{
sb.Append($"{sv.Version}<br/>");
}
await context.Response.WriteAsync(sb.ToString());
}

只要能接收到 IServiceProvider 所引用的实例,就能通过 GetServices 方法获取多个服务实例。

方法二,更简单,直接注入 IEnumerable<T> 类型,本例中就是 IEnumerable<IDemoService>。

public async Task InvokeAsync(HttpContext context, IEnumerable<IDemoService> svs)
{
StringBuilder sb = new StringBuilder();
foreach (var sv in svs)
{
sb.Append($"{sv.Version}<br/>");
}
await context.Response.WriteAsync(sb.ToString());
}

Enumerable<T> 的妙处就是可以 foreach ,这样你也能访问多个实例,而且必要时还可以联合 LINQ 一起耍。

运行结果如下。

最新文章

  1. Scala 笔记
  2. Java 浅析三大特性之一继承
  3. servlet同一用户不同页面共享数据
  4. 剑指offer系列26--正则表达式匹配
  5. 浅析Android中的消息机制(转)
  6. U盘FAT32文件系统
  7. 初学mysql命令
  8. WordPress添加评论回复的电子邮件警报通知
  9. 51Nod 1004 n^n的末位数字(日常复习快速幂,莫名的有毒,卡mod值)
  10. Oauth认证协议
  11. 面试真题--------spring源码解析IOC
  12. Bootstrap开发框架视频整理
  13. linux----------centos下添加环境变量
  14. UML第二次作业:类在类图中的表示
  15. Bootstrap switch 切换状态踩坑
  16. [android] 测试的相关概念
  17. F - Unix ls
  18. 20165321 学习基础与C语言学习心得
  19. 将文件导入到SQL server数据库表中的字段中
  20. Winfrom 基于TCP的Socket服务端 多线程(进阶版)

热门文章

  1. sql server for centos7
  2. Tomcat 管理监控工具
  3. MySQL Error 1170 (42000): BLOB/TEXT Column Used in Key Specification Without a Key Length【转】
  4. iPhone 照片为heic格式怎么处理?
  5. Spring Boot Metrics监控之Prometheus&amp;Grafana(转)
  6. Vue事件 定义方法执行方法 获取数据 改变数据 执行方法传值 以及事件对象
  7. Python之Django之views中视图代码重复查询的优化
  8. 在调用self对象时,本类调用用Win32Info().collect()
  9. 六、postman做ui测试
  10. Jmeter 时间函数工具汇总