如何在ASP.NET Core应用中实现与第三方IoC/DI框架的整合?
我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供了扩展点使我们可以与第三方DI框架进行整合。对此比较了解的读者朋友应该很清楚,针对第三方DI框架的整合可以通过在定义Startup类型的ConfigureServices方法返回一个ServiceProvider来实现。但是真的有这么简单吗?
一、ConfigureServices方法返回的ServiceProvider貌似没有用!?
我们可以通过一个简单的实例来说明这个问题。我们先定义了如下这个一个MyServiceProvider,它实际上是对另一个ServiceProvider的封装。简单起见,我们利用一个字典来保存服务接口与实现类型的映射关系,这个关系可以通过调用Registe方法来注册。在提供服务实例的GetService方法中,如果提供的服务类型已经被注册,我们会创建并返回对应的实例对象,否则我们将利用封装的这个ServiceProvider来提供服务。为了确保服务实例能够被正常回收,如果服务类型实现了IDisposable接口,我们会将它添加到通过字段_disposables表示的集合中。当MyServiceProvider的Dispose方法被调用的时候,提供的这些服务实例的Dispose方法会被调用。
1: public class MyServiceProvider : IServiceProvider, IDisposable
2: {
3: private IServiceProvider _innerServiceProvider;
4: private Dictionary<Type, Type> _services;
5: private List<IDisposable> _disposables;
6:
7: public MyServiceProvider(IServiceProvider innerServiceProvider)
8: {
9: _innerServiceProvider = innerServiceProvider;
10: this._services = new Dictionary<Type, Type>();
11: _disposables = new List<IDisposable>();
12: }
13:
14:
15: public MyServiceProvider Register<TFrom, TTo>() where TTo: TFrom, new()
16: {
17: _services[typeof(TFrom)] = typeof(TTo);
18: return this;
19: }
20:
21: public object GetService(Type serviceType)
22: {
23: Type implementation;
24: if (_services.TryGetValue(serviceType, out implementation))
25: {
26: object service = Activator.CreateInstance(implementation);
27: IDisposable disposbale = service as IDisposable;
28: if (null != disposbale)
29: {
30: _disposables.Add(disposbale);
31: }
32: return service;
33: }
34: return _innerServiceProvider.GetService(serviceType);
35: }
36:
37: public void Dispose()
38: {
39: (_innerServiceProvider as IDisposable)?.Dispose();
40: foreach (var it in _disposables)
41: {
42: it.Dispose();
43: }
44: _disposables.Clear();
45: }
46: }
我们按照如下的方式在一个ASP.NET Core应用中使用MyServiceProvider。如下面的代码片断中,在注册的Starup类型中,我们让ConfigureServices方法返回一个MyServiceProvider对象。服务接口IFoobar和实现类型Foobar之间的映射注册在这个MyServiceProvider对象上。在处理请求的时候,我们利用当前HttpContext对象的RequestServices属性得到为请求处理提供服务的ServiceProvider,并试图利用它得到注册的IFoobar服务。
1: public class Program
2: {
3: public static void Main(string[] args)
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .UseStartup<Startup>()
8: .Build()
9: .Run();
10: }
11: }
12:
13: public class Startup
14: {
15: public IServiceProvider ConfigureServices(IServiceCollection services)
16: {
17: return new MyServiceProvider(services.BuildServiceProvider())
18: .Register<IFoobar, Foobar>();
19: }
20:
21: public void Configure(IApplicationBuilder app)
22: {
23: app.UseDeveloperExceptionPage()
24: .Run(async context => await context.Response.WriteAsync(context.RequestServices.GetRequiredService<IFoobar>().GetType().Name));
25: }
26: }
27: public interface IFoobar { }
28: public class Foobar : IFoobar { }
整个应用就这样简单,貌似也没有什么问题,但是我们启动应用并利用浏览器访问该应用是就会出现如下所示的错误。错误信息表示服务接口IFoobar尚未被注册。

二、原因何在?
我们明明在返回的ServiceProvider注册了IFoobar和Foobar之间的映射关系,为什么RequestServices返回的ServiceProvider说该服务尚未被注册呢?唯一的解释就是ConfigureServices方法返回的ServiceProvider与HttpContext的RequestServices返回的ServiceProvider根本就不是同一个。实际上它们本来就不是同一个对象。
我在《从两个不同的ServiceProvider说起》中曾经谈到过:ConfigureServices方法返回的ServiceProvider将会作为WebHost的ServiceProvider,对于每次接收的请求,WebHost会根据这个ServiceProvider创建一个新的ServiceProvider来作为HttpContext的RequestServices属性,这两个ServiceProvider具有父子管理。照例说,如果RequestServices返回的ServiceProvider是根据ConfigureServices方法返回的ServiceProvider创建的,那么它也应该能够识别注册的服务类型IFoobar,那么为什么依然会出现错误呢?
要了解这个问题,就需要知道这个所谓的“子ServiceProvider”是如何被创建出来的,这其中涉及到ServiceScope的概念。简单来说,ServiceScope是对一个ServiceProvider的封装,前者决定后者的生命周期。ServiceScope由ServiceScopeFactory创建,后者以一个服务的形式注册到“父ServiceProvider”上面。当“父ServiceProvider”需要创建“子ServiceProvider”的时候,它会调用GetService方法得到这个ServiceScopeFactory对象(采用的服务接口为IServiceScopeFactory),并利用后者创建一个ServiceScope,这个ServiceScope提供的ServiceProvider就是返回的“子ServiceProvider”。
但是对于我们的MyServiceProvider对象来说,当调用它的GetService方法试图获取ServiceScopeFactory对象的时候,获取的实际上是被封装的那个SerivceProvider关联的ServiceScopeFactory,那么很自然创建的“子ServiceProvider”也与MyServiceProvider没有什么关系。
三、如何解决这个问题?
既然我们知道了问题的根源,我们自然就有了解决方案。解决方案并不复杂,我们只需要MyServiceProvider的GetService方法返回反映其自身服务注册相关的ServiceScopeFactory。为此我们定义了如下一个ServiceScope和对应的ServiceScopeFactory。
1: internal class ServiceScope : IServiceScope
2: {
3: private MyServiceProvider _serviceProvider;
4:
5: public ServiceScope(IServiceScope innserServiceScope, Dictionary<Type, Type> services)
6: {
7: _serviceProvider = new MyServiceProvider(innserServiceScope.ServiceProvider, services);
8: }
9: public IServiceProvider ServiceProvider
10: {
11: get { return _serviceProvider; }
12: }
13:
14: public void Dispose()
15: {
16: _serviceProvider.Dispose();
17: }
18: }
19:
20: internal class ServiceScopeFactory : IServiceScopeFactory
21: {
22: private IServiceScopeFactory _innerServiceFactory;
23: private Dictionary<Type, Type> _services;
24:
25: public ServiceScopeFactory(IServiceScopeFactory innerServiceFactory, Dictionary<Type, Type> services)
26: {
27: _innerServiceFactory = innerServiceFactory;
28: _services = services;
29: }
30: public IServiceScope CreateScope()
31: {
32: return new ServiceScope(_innerServiceFactory.CreateScope(), _services);
33: }
34: }
除此之外,我们为MyServiceProvider添加了一个构造函数,GetService方法也针对IServiceScopeFactory添加了相应的代码。
1: public class MyServiceProvider : IServiceProvider, IDisposable
2: {
3: public MyServiceProvider(IServiceProvider innerServiceProvider, Dictionary<Type, Type> services)
4: {
5: _innerServiceProvider = innerServiceProvider;
6: _services = services;
7: _disposables = new List<IDisposable>();
8: }
9:
10: public object GetService(Type serviceType)
11: {
12: if (serviceType == typeof(IServiceScopeFactory))
13: {
14: IServiceScopeFactory innerServiceScopeFactory = _innerServiceProvider.GetRequiredService<IServiceScopeFactory>();
15: return new ServiceScopeFactory(innerServiceScopeFactory, _services);
16: }
17: ...
18: }
19: ...
20: }
如何在ASP.NET Core应用中实现与第三方IoC/DI框架的整合?的更多相关文章
- NET Core应用中实现与第三方IoC/DI框架的整合?
NET Core应用中实现与第三方IoC/DI框架的整合? 我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供 ...
- 如何在 ASP.NET Core 测试中操纵时间?
有时候,我们会遇到一些跟系统当前时间相关的需求,例如: 只有开学季才允许录入学生信息 只有到了晚上或者周六才允许备份博客 注册满 3 天的用户才允许进行一些操作 某用户在 24 小时内被禁止发言 很显 ...
- ASP.NET Core SignalR中的流式传输
什么是流式传输? 流式传输是这一种以稳定持续流的形式传输数据的技术. 流式传输的使用场景 有些场景中,服务器返回的数据量较大,等待时间较长,客户端不得不等待服务器返回所有数据后,再进行相应的操作.这时 ...
- 在 ASP.NET Core 项目中使用 AutoMapper 进行实体映射
一.前言 在实际项目开发过程中,我们使用到的各种 ORM 组件都可以很便捷的将我们获取到的数据绑定到对应的 List<T> 集合中,因为我们最终想要在页面上展示的数据与数据库实体类之间可能 ...
- 在 ASP.NET Core 项目中使用 MediatR 实现中介者模式
一.前言 最近有在看 DDD 的相关资料以及微软的 eShopOnContainers 这个项目中基于 DDD 的架构设计,在 Ordering 这个示例服务中,可以看到各层之间的代码调用与我们之前 ...
- 采用最简单的方式在ASP.NET Core应用中实现认证、登录和注销
在安全领域,认证和授权是两个重要的主题.认证是安全体系的第一道屏障,是守护整个应用或者服务的第一道大门.当访问者请求进入的时候,认证体系通过验证对方的提供凭证确定其真实身份.认证体系只有在证实了访问者 ...
- 如何在ASP.NET Core中实现CORS跨域
注:下载本文的完整代码示例请访问 > How to enable CORS(Cross-origin resource sharing) in ASP.NET Core 如何在ASP.NET C ...
- 如何在ASP.NET Core中实现一个基础的身份认证
注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...
- 如何在ASP.NET Core中应用Entity Framework
注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...
随机推荐
- 创建android phonegap项目
一. 首先需要在Eclipse中创建一个Android项目 打开Eclipse,打开File->New->Project 选择Android Application Project点击Ne ...
- 6. SQL Server数据库监控 - 如何告警
原文:6. SQL Server数据库监控 - 如何告警 常用的告警方式大致有:短信.邮件.应用程序 (beep提示,图标提示,升窗提示等),可是不能一直坐在电脑前看着应用程序,或者用脚本部署监控,根 ...
- Java凝视Override、Deprecated、SuppressWarnings详细解释
一.什么是视线 说起目光,你必须先提什么是元数据(metadata). 所谓元数据是数据的数据.那.元数据是描述数据的叙述. 像在表中的数据字段,叙述了这个字段下的数据的含义.而J2SE5.0 ...
- Lex Yacc手册
Python Lex Yacc手册 本文是PLY (Python Lex-Yacc)的中文翻译版.转载请注明出处.这里有更好的阅读体验. 如果你从事编译器或解析器的开发工作,你可能对lex和yacc不 ...
- C#中一些易混淆概念总结
C#中一些易混淆概念 这几天一直在复习C#基础知识,过程中也发现了自己以前理解不清楚和混淆的概念.现在给大家分享出来我的笔记: 一,.NET平台的重要组成部分都是有哪些 1)FCL (所谓的.NET框 ...
- JS数量输入控件
JS数量输入控件 很早看到kissy首页 有数量输入控件,就随便看了下功能 感觉也不怎么难 所以也就试着自己也做了一个, 当然基本的功能和他们的一样,只是用了自己的编码思想来解决这么一个问题.特此给大 ...
- Spring注解:@Resource、@PreConstruct、@PreDestroy、@Component
要使用Spring的注解,必须在XML文件中配置有属性,告诉人家你要使用注解,Spring容器才会去加载类上的注解: <?xml version="1.0" encoding ...
- json在线编辑器
今天搭建了一个json在线的编辑器. 这个主要的功能就是解析和检查json的语法是不是有错误.在使用json的时候,最担心的就是语法的问题了.尤其是自己手动去拼json格式的输出时候. 如图所示,左边 ...
- Android开发效率的小技巧
提高eclipse使用效率(二) 提高Android开发效率的小技巧 XML文件的代码提示 adt中也有xml文件的代码提示,为了让提示来的更加猛烈,我们还要设置一下 打开eclipse - Wi ...
- QQ登录(OAuth2.0)
QQ登录(OAuth2.0) 那些年,我们开发的接口之:QQ登录(OAuth2.0) 吴剑 2013-06-14 原创文章,转载必须注明出处:http://www.cnblogs.com/wujian ...