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: }
作者:蒋金楠 
微信公众账号:大内老A
微博:www.weibo.com/artech

NET Core应用中实现与第三方IoC/DI框架的整合?的更多相关文章

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

    我们知道整个ASP.NET Core建立在以ServiceCollection/ServiceProvider为核心的DI框架上,它甚至提供了扩展点使我们可以与第三方DI框架进行整合.对此比较了解的读 ...

  2. Android面试基础(一)IOC(DI)框架(ViewUtils)讲解_反射和自定义注解类

    1. Android中的IOC(DI)框架 1.1 ViewUtils简介(xUtils中的四大部分之一) IOC: Inverse of Controller 控制反转. DI: Dependenc ...

  3. .net core程序中使用微软的依赖注入框架

    我之前在博文中介绍过Asp.net core下系统自带的依赖注入框架,这个依赖框架在Microsoft.Extensions.DependencyInjection中实现,本身并不是.net core ...

  4. .NET Core部署中你不了解的框架依赖与独立部署

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9703460.html NET Core项目发布的时候你有没有注意到这两个选项呢?有没有纠结过框架依赖与独 ...

  5. 设计模式之初识IoC/DI(六)

    本篇和大家一起学习IoC和DI即控制反转和依赖注入. 当然听上去这词语非常的专业,真不知道是怎么组出来的,看上去难归看上去难,但稍微理解一下也就这么回事了. 首先我们要明白IoC/DI干嘛用的,不然别 ...

  6. asp.net core 四 IOC&DI Autofac

    其实关于IOC,DI已经有了很多的文章,但是自己在使用中还是有很多困惑,而且相信自己使用下,印象还是会比较深刻的 关于这段时间一直在学习.net core,但是这篇文章是比较重要的,也是我自己觉得学习 ...

  7. [ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

    .NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已.承载系统总是采用依赖注入的方式来消费它在服务承载过 ...

  8. Core第三方开源Web框架

    NET Core第三方开源Web框架YOYOFx   YOYOFx框架 YOYOFx是一个轻量级用于构建基于 HTTP 的 Web 服务,基于 .NET 和 Mono 平台. 本着学习的态度,造了这个 ...

  9. .NET CORE——Console中使用依赖注入

    我们都知道,在 ASP.NET CORE 中通过依赖注入的方式来使用服务十分的简单,而在 Console 中,其实也只是稍微绕了个小弯子而已.不管是内置 DI 组件或者第三方的 DI 组件(如Auto ...

随机推荐

  1. python-常用内置函数与装饰器

    1.常用的python函数 abs             求绝对值 all               判断迭代器中所有的数据是否为真或者可迭代数据为空,返回真,否则返回假 any          ...

  2. bzoj3312

    K个硬币,要买N个物品. 给定买的顺序,即按顺序必须是一路买过去,当选定买的东西物品序列后,付出钱后,货主是不会找零钱的.现希望买完所需要的东西后,留下的钱越多越好,如果不能完成购买任务,输出-1 $ ...

  3. HDU5875Function(单调队列)

    The shorter, the simpler. With this problem, you should be convinced of this truth.      You are giv ...

  4. NPM 在MacOSX中的使用技巧

    经常看到有人说『为啥npm install 的时候报错,显示EACCESS错误…』,之前大家都是sudo大法解决问题,也没太在意. 至于这个问题是brew安装工具的时候造成的,还是系统修改磁盘权限造成 ...

  5. P1150 Peter的烟

    题目描述 Peter有n根烟,他每吸完一根烟就把烟蒂保存起来,k(k>1)个烟蒂可以换一个新的烟,那么Peter最终能吸到多少根烟呢? 输入输出格式 输入格式: 每组测试数据一行包括两个整数n( ...

  6. redis设置密码和redis主从复制

    redis设置密码和redis主从复制 一.redis设置密码 1.Redis实用特性 安全性  主从复制(侦听器)事务处理 持久化机制 发布订阅消息 2.安全性:设置客户端连接后进行任何其他指定前需 ...

  7. bzoj 4671 异或图 —— 容斥+斯特林反演+线性基

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4671 首先,考虑容斥,就是设 \( t[i] \) 表示至少有 \( i \) 个连通块的方 ...

  8. @SpringBootApplication注解分析

    首先我们分析的就是入口类Application的启动注解@SpringBootApplication,进入源码: @Target(ElementType.TYPE) @Retention(Retent ...

  9. Haproxy 安装配置详解-端口映射

    Haproxy 简介: HAProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案. HAProxy特别适用于那些负载特大的web站点, ...

  10. httpd服务相关实验

    实验环境: CentOS6.8 1.连接测试: 在/etc/httpd/conf/httpd.conf telnet 172.16.252.242 80 GET /index.html HTTP/1. ...