.NET Core具有一个承载(Hosting)系统,承载需要在后台长时间运行的服务,一个ASP.NET Core应用仅仅是该系统承载的一种服务而已。承载系统总是采用依赖注入的方式来消费它在服务承载过程所需的服务。对于承载系统来说,原始的服务注册总是体现为一个IServiceCollection集合,最终的依赖注入容器则体现为一个IServiceProvider对象,如果要将第三方依赖注入框架整合进来,就需要利用它们解决从IServiceCollection集合到IServiceProvider对象之间的适配问题。

一、IServiceCollection =>ContainerBuilder=>IServiceProvider

具体来说,我们可以在IServiceCollection集合和IServiceProvider对象之间设置一个针对某个第三方依赖注入框架的ContainerBuilder对象。我们先利用包含原始服务注册的IServiceCollection集合来创建一个ContainerBuilder对象,再利用该对象来构建作为依赖注入容器的IServiceProvider对象。

二、 IServiceProviderFactory<TContainerBuilder>

如上图所示的两种转换是利用一个IServiceProviderFactory<TContainerBuilder>对象完成的。如下面的代码片段所示,IServiceProviderFactory<TContainerBuilder>接口定义了两个方法,其中CreateBuilder方法利用指定的IServiceCollection集合创建出对应的ContainerBuilder对象,而CreateServiceProvider方法则进一步利用这个ContainerBuilder对象创建出作为依赖注入容器的IServiceProvider对象。

public interface IServiceProviderFactory<TContainerBuilder>
{
TContainerBuilder CreateBuilder(IServiceCollection services);
IServiceProvider CreateServiceProvider(TContainerBuilder containerBuilder);
}

.NET Core的承载系统总是利用注册的IServiceProviderFactory<TContainerBuilder>服务来创建最终作为依赖注入容器的IServiceProvider对象。承载系统默认注册的是如下这个DefaultServiceProviderFactory类型。如下面的代码片段所示,DefaultServiceProviderFactory对象会直接调用指定IServiceCollection集合的BuildServiceProvider方法创建出对应的IServiceProvider对象。

public class DefaultServiceProviderFactory :  IServiceProviderFactory<IServiceCollection>
{
public DefaultServiceProviderFactory() : this(ServiceProviderOptions.Default){}
public DefaultServiceProviderFactory(ServiceProviderOptions options) =>_options = options; public IServiceCollection CreateBuilder(IServiceCollection services) => services; public IServiceProvider CreateServiceProvider( IServiceCollection containerBuilder) => containerBuilder.BuildServiceProvider(_options);
}

三、整合第三方依赖注入框架

为了让读者朋友对利用注册的IServiceProviderFactory<TContainerBuilder>服务整合第三方依赖注入框架具有更加深刻的理解,我们来演示一个具体的实例。我们在《一个Mini版的依赖注入框架》创建了一个名为Cat的“迷你版”依赖注入框架,接下来我们将提供一个具体IServiceProviderFactory<TContainerBuilder>实现类型完成对它的整合。

我们首先创建一个名为CatBuilder的类型作为对应的ContainerBuilder。由于需要涉及针对服务范围的创建,我们在CatBuilder类中定了如下两个内嵌的私有类型,其中表示服务范围的ServiceScope对象实际上就是对一个IServiceProvider对象的封装,另一个ServiceScopeFactory类型表示创建该对象的工厂,它是对一个Cat对象的封装。

public class CatBuilder
{
private class ServiceScope : IServiceScope
{
public ServiceScope(IServiceProvider serviceProvider) => ServiceProvider = serviceProvider;
public IServiceProvider ServiceProvider { get; }
public void Dispose()=> (ServiceProvider as IDisposable)?.Dispose();
} private class ServiceScopeFactory : IServiceScopeFactory
{
private readonly Cat _cat;
public ServiceScopeFactory(Cat cat) => _cat = cat;
public IServiceScope CreateScope() => new ServiceScope(_cat);
}
}

一个CatBuilder对象是对一个Cat对象的封装,它的BuildServiceProvider方法会直接返回这个Cat对象,并作为最终提供的依赖注入容器。CatBuilder在初始化过程中添加了针对IServiceScopeFactory接口的服务注册,具体注册的是根据作为当前子容器的Cat对象创建的ServiceScopeFactory对象。为了实现程序集范围内的批量服务注册,我们为CatBuilder定义了一个Register方法。

public class CatBuilder
{
private readonly Cat _cat;
public CatBuilder(Cat cat)
{
_cat = cat;
_cat.Register<IServiceScopeFactory>( c => new ServiceScopeFactory(c.CreateChild()), Lifetime.Transient);
}
public IServiceProvider BuildServiceProvider() => _cat;
public CatBuilder Register(Assembly assembly)
{
_cat.Register(assembly);
return this;
}
...
}

如下所示的CatServiceProviderFactory类型实现了IServiceProviderFactory<CatBuilder>接口。在实现的CreateBuilder方法中,我们创建了一个Cat对象,并将指定IServiceCollection集合包含中的服务注册(ServiceDescriptor对象)转换成兼容Cat的服务注册(ServiceRegistry对象)并应用到创建的Cat对象上。我们最终利用这个Cat对象创建出返回的CatBuilder对象。实现的另一个方法CreateServiceProvider返回的是调用CatBuilder对象的CreateServiceProvider方法得到的IServiceProvider对象。

public class CatServiceProviderFactory : IServiceProviderFactory<CatBuilder>
{
public CatBuilder CreateBuilder(IServiceCollection services)
{
var cat = new Cat();
foreach (var service in services)
{
if (service.ImplementationFactory != null)
{
cat.Register(service.ServiceType, provider ) => service.ImplementationFactory(provider), service.Lifetime.AsCatLifetime());
}
else if (service.ImplementationInstance != null)
{
cat.Register(service.ServiceType, service.ImplementationInstance);
}
else
{
cat.Register(service.ServiceType, service.ImplementationType, service.Lifetime.AsCatLifetime());
}
}
return new CatBuilder(cat);
}
public IServiceProvider CreateServiceProvider(CatBuilder containerBuilder) => containerBuilder.BuildServiceProvider();
}

Cat具有.NET Core依赖注入框架一致的服务生命周期表达方式,所以我们在将服务注册从ServiceDescriptor类型转化成ServiceRegistry类型时,可以实现直接完成两种生命周期模式的转换,具体的转换实现在如下这个AsCatLifetime扩展方法中。

internal static class Extensions
{
public static Lifetime AsCatLifetime(this ServiceLifetime lifetime)
{
return lifetime switch
{
ServiceLifetime.Scoped => Lifetime.Self,
ServiceLifetime.Singleton => Lifetime.Root,
_ => Lifetime.Transient,
};
}
}

接下来我们演示如何利用CatServiceProviderFactory来创建作为依赖注入容器的IServiceProvider对象。我们定义了如下的接口和对应的实现类型,其中Foo、Bar、Baz和Qux类型分别实现了对应的接口IFoo、IBar、IBaz和IQux,其中Qux类型上标注了一个MapToAttribute特性注册了与对应接口IQux之间的映射。为了反映Cat对服务实例生命周期的控制,我们让它们派生于同一个基类Base。Base实现了IDisposable接口,我们在其构造函数和实现的Dispose方法中输出相应的文本以确定对应的实例何时被创建和释放。

public interface IFoo {}
public interface IBar {}
public interface IBaz {}
public interface IQux {}
public interface IFoobar<T1, T2> {}
public class Base : IDisposable
{
public Base() => Console.WriteLine($"Instance of {GetType().Name} is created.");
public void Dispose() => Console.WriteLine($"Instance of {GetType().Name} is disposed.");
} public class Foo : Base, IFoo{ }
public class Bar : Base, IBar{ }
public class Baz : Base, IBaz{ }
[MapTo(typeof(IQux), Lifetime.Root)]
public class Qux : Base, IQux { }
public class Foobar<T1, T2>: IFoobar<T1,T2>
{
public IFoo Foo { get; }
public IBar Bar { get; }
public Foobar(IFoo foo, IBar bar)
{
Foo = foo;
Bar = bar;
}
}

在如下所示的演示程序中,我们创建了一个ServiceCollection集合,并采用三种不同的生命周期模式分别添加了针对IFoo、IBar和IBaz接口的服务注册。我们接下来根据这个ServiceCollection集合创建了一个CatServiceProviderFactory对象,并调用其CreateBuilder方法创建出对应的CatBuilder对象。我们随后调用了CatBuilder对象的Register方法完成了针对当前入口程序集的批量服务注册,其目的在于添加针对IQux/Qux的服务注册。

class Program
{
static void Main()
{
var services = new ServiceCollection()
.AddTransient<IFoo, Foo>()
.AddScoped<IBar>(_ => new Bar())
.AddSingleton<IBaz>(new Baz()); var factory = new CatServiceProviderFactory();
var builder = factory.CreateBuilder(services)
.Register(Assembly.GetEntryAssembly());
var container = factory.CreateServiceProvider(builder); GetServices();
GetServices();
Console.WriteLine("\nRoot container is disposed.");
(container as IDisposable)?.Dispose(); void GetServices()
{
using (var scope = container.CreateScope())
{
Console.WriteLine("\nService scope is created.");
var child = scope.ServiceProvider; child.GetService<IFoo>();
child.GetService<IBar>();
child.GetService<IBaz>();
child.GetService<IQux>(); child.GetService<IFoo>();
child.GetService<IBar>();
child.GetService<IBaz>();
child.GetService<IQux>();
Console.WriteLine("\nService scope is disposed.");
}
}
}
}

在调用CatServiceProviderFactory对象的CreateServiceProvider方法创建出作为依赖注入容器的IServiceProvider对象之后,我们先后两次调用了本地方法GetServices方法。GetServices方法会利用这个IServiceProvider对象创建一个服务范围,并利用此服务范围内的IServiceProvider提供两组服务实例。通过CatServiceProviderFactory创建的IServiceProvider对象在最终通过调用其Dispose方法进行释放。该程序运行之后会在控制台上输出如图4-16所示的结果,输出结果体现的服务生命周期与演示程序体现的是完全一致的。

[ASP.NET Core 3框架揭秘] 依赖注入[1]:控制反转
[ASP.NET Core 3框架揭秘] 依赖注入[2]:IoC模式
[ASP.NET Core 3框架揭秘] 依赖注入[3]:依赖注入模式
[ASP.NET Core 3框架揭秘] 依赖注入[4]:一个迷你版DI框架
[ASP.NET Core 3框架揭秘] 依赖注入[5]:利用容器提供服务
[ASP.NET Core 3框架揭秘] 依赖注入[6]:服务注册
[ASP.NET Core 3框架揭秘] 依赖注入[7]:服务消费
[ASP.NET Core 3框架揭秘] 依赖注入[8]:服务实例的生命周期
[ASP.NET Core 3框架揭秘] 依赖注入[9]:实现概述
[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配

[ASP.NET Core 3框架揭秘] 依赖注入[10]:与第三方依赖注入框架的适配的更多相关文章

  1. ASP.NET Core Web 应用程序系列(一)- 使用ASP.NET Core内置的IoC容器DI进行批量依赖注入(MVC当中应用)

    在正式进入主题之前我们来看下几个概念: 一.依赖倒置 依赖倒置是编程五大原则之一,即: 1.上层模块不应该依赖于下层模块,它们共同依赖于一个抽象. 2.抽象不能依赖于具体,具体依赖于抽象. 其中上层就 ...

  2. Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

  3. Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录

    1.登录的实现 登录功能实现起来有哪些常用的方式,大家首先想到的肯定是cookie或session或cookie+session,当然还有其他模式,今天主要探讨一下在Asp.net core 2.0下 ...

  4. ASP.NET Core 6框架揭秘实例演示[04]:自定义依赖注入框架

    ASP.NET Core框架建立在一个依赖注入框架之上,已注入的方式消费服务已经成为了ASP.NET Core基本的编程模式.为了使读者能够更好地理解原生的注入框架框架,我按照类似的设计创建了一个简易 ...

  5. ASP.NET Core 6框架揭秘实例演示[10]:Options基本编程模式

    依赖注入使我们可以将依赖的功能定义成服务,最终以一种松耦合的形式注入消费该功能的组件或者服务中.除了可以采用依赖注入的形式消费承载某种功能的服务,还可以采用相同的方式消费承载配置数据的Options对 ...

  6. ASP.NET CORE MVC 2.0 如何在Filter中使用依赖注入来读取AppSettings,及.NET Core控制台项目中读取AppSettings

    问: ASP.NET CORE MVC 如何在Filter中使用依赖注入来读取AppSettings 答: Dependency injection is possible in filters as ...

  7. ASP.NET Core:使用Dapper和SwaggerUI来丰富你的系统框架

    一.概述 1.用VS2017创建如下图的几个.NET Standard类库,默认版本为1.4,你可以通过项目属性进行修改,最高支持到1.6,大概五月份左右会更新至2.0,API会翻倍,很期待! 排名分 ...

  8. 菜鸟入门【ASP.NET Core】6:配置的热更新、配置的框架设计

    配置的热更新 什么是热更新:这个词听着有点熟悉,但到底是什么呢? 一般来说:创建的项目都无法做到热更新:即项目无需重启,修改配置文件后读取到的信息就是修改配置之后的 我们只需要吧项目中用到的IOpti ...

  9. 从ASP.Net Core Web Api模板中移除MVC Razor依赖项

    前言 :本篇文章,我将会介绍如何在不包括MVC / Razor功能和包的情况下,添加最少的依赖项到ASP.NET Core Web API项目中. 一.MVC   VS WebApi (1)在ASP. ...

  10. 【ASP.NET Core】准备工作:在 Windows 10 上配置 Linux 子系统

    ASP.NET Core 其实比传统的 ASP.NET 要简单很多,而且也灵活很多,并且可以跨平台独立运行. 在 Windows 平台上,我们只要在安装 Visual Studio 的时候选择跨平台的 ...

随机推荐

  1. Python 编程语言要掌握的技能之一:使用数字与字符串的技巧

    最佳实践 1. 少写数字字面量 “数字字面量(integer literal)” 是指那些直接出现在代码里的数字.它们分布在代码里的各个角落,比如代码 del users[0] 里的 0 就是一个数字 ...

  2. IDEA导入MySQL的jdbc驱动,并操作数据库

    将MySQL的jdbc驱动,导入IDEA的方式,虽然也能连接并且操作数据库,但并不推荐这种方式,推荐使用Maven工程的方式:https://www.cnblogs.com/dadian/p/1193 ...

  3. TraceID在AspNETCore日志排障中的应用

    前言 .NetCore日志,相信大家多少都接触过,博客园有关 ① AspNetCore依赖注入第三方日志组件   ②第三方日志组件Nlog,Serilog 应用方法的博文层出不穷. 结合程序的部署结构 ...

  4. 小白学习python第一天,Pycharm破解与用法(持续更新)

    目录 Pycharm安装与破解及汉化 Pycharm安装 Pycharm破解 Pycharm汉化 Pycharm使用 添加作者.时间等信息 补充 @ Pycharm安装与破解及汉化 本人最近开始找到了 ...

  5. Kotlin协程通信机制: Channel

    Coroutines Channels Java中的多线程通信, 总会涉及到共享状态(shared mutable state)的读写, 有同步, 死锁等问题要处理. 协程中的Channel用于协程间 ...

  6. 影响ES相关度算分的因素

    相关性算分 指文档与查询语句间的相关度,通过倒排索引可以获取与查询语句相匹配的文档列表   如何将最符合用户查询需求的文档放到前列呢? 本质问题是一个排序的问题,排序的依据是相关性算分,确定倒排索引哪 ...

  7. .net core 学习 读取配置文件

    在空项目中是没有配置文件的,首先要新建一个,配置文件内容如下,下面来读取各个内容 { "ConnectionStrings": { "DefaultConnection& ...

  8. 品优购详情页---产品详细信息区域 iteminfo_wrap

    产品详细信息区域为整个大盒子命名为: iteminfo_wrap 1号盒子命名为:sku_name 2号盒子命名为:news 3号盒子命名为:summary step1:3个盒子搭建框架,以及完成前两 ...

  9. js中this的使用及代表意义

    我们在js中经常看到this这个关键字,那么他是什么呢?它可以是全局对象.当前对象,也可以是任意对象,函数的调用方式决定了 this 的值. 1. 方法中的this. 在对象方法中, this 指向调 ...

  10. centos7 安装wps

    # cat /etc/redhat-release CentOS Linux release 7.6.1810 (Core) # cat /proc/version Linux version 3.1 ...