我们知道整个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框架的整合?的更多相关文章

  1. NET Core应用中实现与第三方IoC/DI框架的整合?

    NET Core应用中实现与第三方IoC/DI框架的整合? 我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供 ...

  2. 如何在 ASP.NET Core 测试中操纵时间?

    有时候,我们会遇到一些跟系统当前时间相关的需求,例如: 只有开学季才允许录入学生信息 只有到了晚上或者周六才允许备份博客 注册满 3 天的用户才允许进行一些操作 某用户在 24 小时内被禁止发言 很显 ...

  3. ASP.NET Core SignalR中的流式传输

    什么是流式传输? 流式传输是这一种以稳定持续流的形式传输数据的技术. 流式传输的使用场景 有些场景中,服务器返回的数据量较大,等待时间较长,客户端不得不等待服务器返回所有数据后,再进行相应的操作.这时 ...

  4. 在 ASP.NET Core 项目中使用 AutoMapper 进行实体映射

    一.前言 在实际项目开发过程中,我们使用到的各种 ORM 组件都可以很便捷的将我们获取到的数据绑定到对应的 List<T> 集合中,因为我们最终想要在页面上展示的数据与数据库实体类之间可能 ...

  5. 在 ASP.NET Core 项目中使用 MediatR 实现中介者模式

    一.前言  最近有在看 DDD 的相关资料以及微软的 eShopOnContainers 这个项目中基于 DDD 的架构设计,在 Ordering 这个示例服务中,可以看到各层之间的代码调用与我们之前 ...

  6. 采用最简单的方式在ASP.NET Core应用中实现认证、登录和注销

    在安全领域,认证和授权是两个重要的主题.认证是安全体系的第一道屏障,是守护整个应用或者服务的第一道大门.当访问者请求进入的时候,认证体系通过验证对方的提供凭证确定其真实身份.认证体系只有在证实了访问者 ...

  7. 如何在ASP.NET Core中实现CORS跨域

    注:下载本文的完整代码示例请访问 > How to enable CORS(Cross-origin resource sharing) in ASP.NET Core 如何在ASP.NET C ...

  8. 如何在ASP.NET Core中实现一个基础的身份认证

    注:本文提到的代码示例下载地址> How to achieve a basic authorization in ASP.NET Core 如何在ASP.NET Core中实现一个基础的身份认证 ...

  9. 如何在ASP.NET Core中应用Entity Framework

    注:本文提到的代码示例下载地址> How to using Entity Framework DB first in ASP.NET Core 如何在ASP.NET Core中应用Entity ...

随机推荐

  1. javascript and dom1

    <script type="text/javascript"> //数组 var beatles=Array(4); var beatles=Array(); cons ...

  2. MonkeyDevcie API 实践全记录

    1.    背景 使用SDK自带的NotePad应用作为实践目标应用,目的是对MonkeyDevice拥有的成员方法做一个初步的了解. 以下是官方列出的方法的Overview. Return Type ...

  3. asp.net读取CSV

    原文:asp.net读取CSV 用Excel导了两天数据,各种问题,折磨客户也折磨了自己,以前没发现的问题一下子都暴露出来了 特意收集两篇Excel跟CSV读取相关的两篇文章 asp.net读取exc ...

  4. API接口开发简述示例

    作为最流行的服务端语言PHP(PHP: Hypertext Preprocessor),在开发API方面,是很简单且极具优势的.API(Application Programming Interfac ...

  5. location将地址栏参数拆分成键值对的对象

    window.location可获取地址栏的一系列信息,并且每个浏览器都支持该属性,非常方便.而获取到的问号后面的参数可以进行加工转变成我们所想要的键值对. location的属性: 属性名 例子 说 ...

  6. 正则提取文本中的颜色值 #xxxx,不严谨版本

    不严谨但兼容性最好: #[a-fA-F0-9]+ 增加严谨性: #[a-fA-F0-9]{1,6}

  7. erlang mnesia数据库简单应用

    mnesia是erlang自带的分布式数据库,基于ets和dets实现的.mnesia兼顾了dets的持久性和ets的高性能,可以自动在多个erlang节点间同步数据库.最关键的是,mnesia实现了 ...

  8. HDU 5185 Equation (DP)

    题目:LINK 题意:求满足题目要求的x序列的种类数. 能够发现符合条件的序列去重后是一个0, 1, ..., k的连续序列(k满足k*(k+1)/2 <= n) ,则这个去重后的序列长度最长为 ...

  9. CRT

    G - 中国剩余定理 Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Submit S ...

  10. SQL远程备份

    原文:SQL远程备份 set ANSI_NULLS ON set QUOTED_IDENTIFIER ON go   -- ====================================== ...