依赖注入在 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 一起耍。

运行结果如下。

接口定义

public interface ITest
{
string Name { get; }
void Do();
} public class TestA : ITest
{
public string Name => "a";
public void Do() => Console.Write("a");
} public class TestB : ITest
{
public string Name => "b";
public void Do() => Console.Write("b");
}

依赖注入方法

直接注入实现类的方式

services.AddTransient<TestA>();
services.AddTransient<TestB>(); public HomeController(TestA testA)
{
......
} serviceProvider.GetService<TestA>();

使用集合的注入方式

services.AddTransient<ITest, TestA>();
services.AddTransient<ITest, TestB>(); public HomeController(IEnumerable<ITest> tests)
{
var testa = tests.FirstOrDefault(p => p.Name == "a");
} serviceProvider.GetServices<ITest>().ToList().FirstOrDefault(p => p.Name == "a");

使用Func工厂的注入方式

注意,注册Func工厂是一定要使用单例模式

services.AddTransient<TestA>();
services.AddTransient<TestB>();
services.AddSingleton(provider =>
{
Func<string, ITest> accesor = key =>
{
switch (key)
{
case “a”:
return provider.GetService<TestA>();
case "b":
return provider.GetService<TestB>();
default:
throw new NotSupportedException($"Not Support key : {key}");
}
};
return accesor;
}); public HomeController(Func<string, IOceanOrderHandleStrategy> serviceAccessor)
{
var testa = serviceAccessor("a");
} var func = serviceProvider.GetServices<Func<string, IOceanOrderHandleStrategy>>();
var testa = func("a");

使用工厂类注入

同样要注意,这里注册只能是单例模式

SingletonFactory singletonFactory = new SingletonFactory();
singletonFactory.AddService<ITest>(new TestA(), "a");
singletonFactory.AddService<ITest>(new TestB(), "b");
services.AddSingleton(singletonFactory); public HomeController(SingletonFactory singletonFactory)
{
//使用标识从SingletonFactory获取自己想要的服务实现
var testa = singletonFactory.GetService<ITest>("a");
}

SingletonFactory.cs

public class SingletonFactory
{ Dictionary<Type, Dictionary<string, object>> serviceDic; public SingletonFactory()
{
serviceDic = new Dictionary<Type, Dictionary<string, object>>();
} public void AddService<TService>(string id, TService service) where TService : class
{
AddService(typeof(TService), service, id);
} public void AddService(string id, Type serviceType, object service)
{
if (service != null)
{
if (serviceDict.TryGetValue(serviceType, out Dictionary<string, object> implDict))
{
implDict[id] = service;
}
else
{
implDict = new Dictionary<string, object>();
implDict[id] = service;
serviceDict[serviceType] = implDict;
}
}
} public TService GetService<TService>(string id) where TService : class
{
var serviceType = typeof(TService);
return GetService<TService>(serviceType, id);
} public TService GetService<TService>(Type serviceType, string id) where TService : class
{
if (serviceDic.TryGetValue(serviceType, out Dictionary<string, object> implDic))
{
if (implDic.TryGetValue(id, out object service))
{
return service as TService;
}
}
return null;
}
}

NET 5 依赖注入多个服务实现类的更多相关文章

  1. ASP.NET Core依赖注入多个服务实现类

    依赖注入在 ASP.NET Core 中起中很重要的作用,也是一种高大上的编程思想,它的总体原则就是:俺要啥,你就给俺送啥过来. 服务类型的实例转由容器自动管理,无需我们在代码中显式处理. 因此,有了 ...

  2. [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期

    生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...

  3. [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费

    包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IService ...

  4. [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册

    通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...

  5. 依赖注入(DI)与服务容器(IoC)

    参考文章:http://www.yuansir-web.com/2014/03/20/%E7%90%86%E8%A7%A3php-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A ...

  6. YII框架的依赖注入容器与服务定位器简述

    依赖注入容器 依赖注入(Dependency Injection,DI)容器就是一个对象use yii\di\Container,它知道怎样初始化并配置对象及其依赖的所有对象. 依赖注入和服务定位器都 ...

  7. 【ASP.NET Core】依赖注入高级玩法——如何注入多个服务实现类

    依赖注入在 ASP.NET Core 中起中很重要的作用,也是一种高大上的编程思想,它的总体原则就是:俺要啥,你就给俺送啥过来.服务类型的实例转由容器自动管理,无需我们在代码中显式处理. 因此,有了依 ...

  8. 项目案例【Net Core】如何注入多个服务实现类

    需求 库表保存时,需要校验逻辑. 提交时有更深层次校验. **状态,还有特殊校验 接口 写一个通用的校验接口,这里定义了校验时间.每个阶段校验可能需要考虑顺序,增加一个顺序字段. public int ...

  9. ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法

    在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...

随机推荐

  1. 接上一篇:(三) Spring环境搭建

    3.1.获取 Spring framework jar 包 (一) spring官网下载 (二)spring的核心包 (三) 配置 XML 1. 新建立一个 xml.名字任意,如 applicatio ...

  2. kafka producer 概要(看源码前,最好能掌握)

        kafakproducer概要(看源码前,最好能理解) 摘要 kafak 被设计用来作为一个统一的平台来处理庞大的数据的实时工具,在设计上有诸多变态的要求 它必须具有高吞吐量才能支持大量事件流 ...

  3. 第四代Express框架koa简介

    目录 简介 koa和express koa使用介绍 中间件的级联关系 koa的构造函数 启动http server 自定义中间件 异常处理 简介 熟悉Spring MVC的朋友应该都清楚Spring ...

  4. pixi.js持续渲染页面

    Pixi是一个超快的2D渲染引擎,通过Javascript和Html技术创建动画或管理交互式图像,从而制作游戏或应用. 项目地址:https://github.com/pixijs/pixi.js A ...

  5. 3D显微镜笔记

    1. 三视图:能够正确反映物体长.宽.高尺寸的正投影工程图(主视图,俯视图,左视图三个基本视图)为三视图,这是工程界一种对物体几何形状约定俗成的抽象表达方式. 附上自己大二时候设计的减速器--设计了两 ...

  6. 第8.16节 Python重写自定义类的__str__方法

    一. 引言 上节结合案例介绍了重写__repr__方法的关注点,重写__repr__方法的要点是要准确的输出开发人员关注的信息,并便于开发人员使用相关信息.而__str__方法是为最终用户返回类的相关 ...

  7. Python中repr(变量)和str(变量)的返回值有什么区别和联系

    Python中repr(变量)和str(变量)都返回一个描述对象的字符串,二者有关联又有不同.由于Python3.0后都是新式类,我们的分析也是基于新式类进行的.基于object派生的新式类中二者之间 ...

  8. 第2章 Python编程基础知识目录

    第2.1节 简单的Python数据类型.变量赋值及输入输出 第2.2节 Python的语句 第2.3节 Python运算符大全 老猿Python,跟老猿学Python! 博客地址:https://bl ...

  9. spring框架半自动注解

    为了简便我们的开发,让我们一起来学习半自动注解吧. 让Spring管理某些类 1.在需要被SpringIOC容器管理的类上打上相应的注解 @Component:任意组件 @Controller:控制层 ...

  10. 彻底搞懂js __proto__ prototype constructor

    在开始之前,必须要知道的是:对象具有__proto__.constructor(函数也是对象固也具有以上)属性,而函数独有prototype 在博客园看到一张图分析到位很彻底,这里共享: 刚开始看这图 ...