NET 5 依赖注入多个服务实现类
依赖注入在 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 依赖注入多个服务实现类的更多相关文章
- ASP.NET Core依赖注入多个服务实现类
依赖注入在 ASP.NET Core 中起中很重要的作用,也是一种高大上的编程思想,它的总体原则就是:俺要啥,你就给俺送啥过来. 服务类型的实例转由容器自动管理,无需我们在代码中显式处理. 因此,有了 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
生命周期决定了IServiceProvider对象采用怎样的方式提供和释放服务实例.虽然不同版本的依赖注入框架针对服务实例的生命周期管理采用了不同的实现,但总的来说原理还是类似的.在我们提供的依赖注入 ...
- [ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
包含服务注册信息的IServiceCollection集合最终被用来创建作为依赖注入容器的IServiceProvider对象.当需要消费某个服务实例的时候,我们只需要指定服务类型调用IService ...
- [ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
通过<利用容器提供服务>我们知道作为依赖注入容器的IServiceProvider对象是通过调用IServiceCollection接口的扩展方法BuildServiceProvider创 ...
- 依赖注入(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 ...
- YII框架的依赖注入容器与服务定位器简述
依赖注入容器 依赖注入(Dependency Injection,DI)容器就是一个对象use yii\di\Container,它知道怎样初始化并配置对象及其依赖的所有对象. 依赖注入和服务定位器都 ...
- 【ASP.NET Core】依赖注入高级玩法——如何注入多个服务实现类
依赖注入在 ASP.NET Core 中起中很重要的作用,也是一种高大上的编程思想,它的总体原则就是:俺要啥,你就给俺送啥过来.服务类型的实例转由容器自动管理,无需我们在代码中显式处理. 因此,有了依 ...
- 项目案例【Net Core】如何注入多个服务实现类
需求 库表保存时,需要校验逻辑. 提交时有更深层次校验. **状态,还有特殊校验 接口 写一个通用的校验接口,这里定义了校验时间.每个阶段校验可能需要考虑顺序,增加一个顺序字段. public int ...
- ASP.NET Core依赖注入系统学习教程:关于服务注册使用到的方法
在.NET Core的依赖注入框架中,服务注册的信息将会被封装成ServiceDescriptor对象,而这些对象都会存储在IServiceCollection接口类型表示的集合中,另外,IServi ...
随机推荐
- Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合
点波关注不迷路,一键三连好运连连! 先贴上几个案例,水平高超的同学可以挑战一下: 从员工集合中筛选出salary大于8000的员工,并放置到新的集合里. 统计员工的最高薪资.平均薪资.薪资之和. 将员 ...
- Java基础教程——网络基础知识
参考阅读[中国互联网发展史]:https://zhuanlan.zhihu.com/p/61602252 协议 计算机网络中的通信必须有一些约定,这些约定称为"通信协议". 通信协 ...
- 盘点腾讯Linux、 C++后台开发面试题,做好充足准备,不怕被Pass
一.C/C++ const 多态 什么类不能被继承 二.网络 网络的字节序 网络知识 TCP三次握手 各种细节 timewait状态 TCP与UDP的区别 概念 适用范围 TCP四次挥 ...
- Django的静态文件的配置
静态文件配置 STATIC_URL = '/static/' # 静态文件配置 STATICFILES_DIRS = [ os.path.join(BASE_DIR,'static') ] # 暴露给 ...
- rest-framework 解析器
一 解析器的作用: 根据请求头 content-type 选择对应的解析器对请求体内容进行处理. 有application/json,x-www-form-urlencoded,form-data等格 ...
- Integer中的奇妙位运算
Integer中的奇妙位运算 参考资料 https://segmentfault.com/a/1190000015763941 highestOneBit(int i) 函数的作用是获得传入参数的最高 ...
- day3(axios封装)
1. 始vue化项目 https://www.cnblogs.com/xiaonq/p/11027880.html vue init webpack deaxios # 使用脚手架创建项目 d ...
- moviepy音视频剪辑:使用fl_time进行时间特效处理报错OSError: Error in file xxxx, Accessing time
☞ ░ 前往老猿Python博文目录 ░ 老猿在使用moviepy音视频剪辑的fl_time进行时间特效处理时,系统报错: OSError: Error in file F:\video\WinBas ...
- 第6.2节 Python特色的动态可执行方法简介
一. 基本概念 动态可执行,是指在代码中通过外部输入或代码嵌入的常量字符串包含代码的方式提供Python代码,要求Python执行这些代码.这样就可以达到开放式运行的效果,提高程序的能力和灵活性 ...
- flask中的重定向,渲染,反转视图函数
在学习flask中,重定向,渲染,反转老是不怎么明白,今天明白了其中的点了,来给大家分享下 rend_templete()这个函数就是一个渲染的作用,渲染html的东西. url_for是反转视图函数 ...