音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

ASP.NET Core应用中与第三方IoC/DI框架的整合
日期:2021-09-07 22:27:11   来源:脚本之家

一、ConfigureServices方法返回的ServiceProvider没有用!

我们可以通过一个简单的实例来说明这个问题。我们先定义了如下这个一个MyServiceProvider,它实际上是对另一个ServiceProvider的封装。简单起见,我们利用一个字典来保存服务接口与实现类型的映射关系,这个关系可以通过调用Registe方法来注册。在提供服务实例的GetService方法中,如果提供的服务类型已经被注册,我们会创建并返回对应的实例对象,否则我们将利用封装的这个ServiceProvider来提供服务。为了确保服务实例能够被正常回收,如果服务类型实现了IDisposable接口,我们会将它添加到通过字段_disposables表示的集合中。当MyServiceProvider的Dispose方法被调用的时候,提供的这些服务实例的Dispose方法会被调用。

 public class MyServiceProvider : IServiceProvider, IDisposable
 {
 private IServiceProvider _innerServiceProvider;
 private Dictionary<Type, Type> _services;
 private List<IDisposable> _disposables;
 
 public MyServiceProvider(IServiceProvider innerServiceProvider)
 {
 _innerServiceProvider = innerServiceProvider;
 this._services = new Dictionary<Type, Type>();
 _disposables = new List<IDisposable>();
 }
 
 
 public MyServiceProvider Register<TFrom, TTo>() where TTo: TFrom, new()
 {
 _services[typeof(TFrom)] = typeof(TTo);
 return this;
 }
 
 public object GetService(Type serviceType)
 {
 Type implementation;
 if (_services.TryGetValue(serviceType, out implementation))
 {
 object service = Activator.CreateInstance(implementation);
 IDisposable disposbale = service as IDisposable;
 if (null != disposbale)
 {
  _disposables.Add(disposbale);
 }
 return service;
 }
 return _innerServiceProvider.GetService(serviceType);
 }
 
 public void Dispose()
 {
 (_innerServiceProvider as IDisposable)?.Dispose();
 foreach (var it in _disposables)
 {
 it.Dispose();
 }
 _disposables.Clear();
 }
 }

我们按照如下的方式在一个ASP.NET Core应用中使用MyServiceProvider。如下面的代码片断中,在注册的Starup类型中,我们让ConfigureServices方法返回一个MyServiceProvider对象。服务接口IFoobar和实现类型Foobar之间的映射注册在这个MyServiceProvider对象上。在处理请求的时候,我们利用当前HttpContext对象的RequestServices属性得到为请求处理提供服务的ServiceProvider,并试图利用它得到注册的IFoobar服务。

 public class Program
 {
 public static void Main(string[] args)
 {
 new WebHostBuilder()
 .UseKestrel()
 .UseStartup<Startup>()
 .Build()
 .Run();
 }
 }
 
 public class Startup
 {
 public IServiceProvider ConfigureServices(IServiceCollection services)
 {
 return new MyServiceProvider(services.BuildServiceProvider())
 .Register<IFoobar, Foobar>();
 }
 
 public void Configure(IApplicationBuilder app)
 {
 app.UseDeveloperExceptionPage()
 .Run(async context => await context.Response.WriteAsync(context.RequestServices.GetRequiredService<IFoobar>().GetType().Name));
 }
 }
 public interface IFoobar { }
 public class Foobar : IFoobar { }

整个应用就这样简单,貌似也没有什么问题,但是我们启动应用并利用浏览器访问该应用是就会出现如下所示的错误。错误信息表示服务接口IFoobar尚未被注册。

二、原因何在?

我们明明在返回的ServiceProvider注册了IFoobar和Foobar之间的映射关系,为什么RequestServices返回的ServiceProvider说该服务尚未被注册呢?唯一的解释就是ConfigureServices方法返回的ServiceProvider与HttpContext的RequestServices返回的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。

 internal class ServiceScope : IServiceScope
 {
 private MyServiceProvider _serviceProvider;
 
 public ServiceScope(IServiceScope innserServiceScope, Dictionary<Type, Type> services)
 {
 _serviceProvider = new MyServiceProvider(innserServiceScope.ServiceProvider, services);
 }
 public IServiceProvider ServiceProvider
 {
 get { return _serviceProvider; }
 }
 
 public void Dispose()
 {
 _serviceProvider.Dispose();
 }
 }
 
 internal class ServiceScopeFactory : IServiceScopeFactory
 {
 private IServiceScopeFactory _innerServiceFactory;
 private Dictionary<Type, Type> _services;
 
 public ServiceScopeFactory(IServiceScopeFactory innerServiceFactory, Dictionary<Type, Type> services)
 {
 _innerServiceFactory = innerServiceFactory;
 _services = services;
 }
 public IServiceScope CreateScope()
 {
 return new ServiceScope(_innerServiceFactory.CreateScope(), _services);
 }
 }

除此之外,我们为MyServiceProvider添加了一个构造函数,GetService方法也针对IServiceScopeFactory添加了相应的代码。

 public class MyServiceProvider : IServiceProvider, IDisposable
{
 public MyServiceProvider(IServiceProvider innerServiceProvider, Dictionary<Type, Type> services)
 {
 _innerServiceProvider = innerServiceProvider;
 _services = services;
 _disposables = new List<IDisposable>();
 }
 
 public object GetService(Type serviceType)
 {
 if (serviceType == typeof(IServiceScopeFactory))
 {
 IServiceScopeFactory innerServiceScopeFactory = _innerServiceProvider.GetRequiredService<IServiceScopeFactory>();
 return new ServiceScopeFactory(innerServiceScopeFactory, _services);
 }
 ... 
 }
 ...
 }

以上分享,希望能对需要解决这样问题的朋友有所帮助!

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    ASP编程JSP编程PHP编程.NET编程python编程