ASP.NET Core框架建立在一个依赖注入框架之上,已注入的方式消费服务已经成为了ASP.NET Core基本的编程模式。为了使读者能够更好地理解原生的注入框架框架,我按照类似的设计创建了一个简易版本的依赖注入框架,并它命名为“Cat”。本篇提供的四个实例主要体现了针对Cat的用法,《一个Mini版的依赖注入框架》提供了针对设计和实现原理的介绍。

[201]模拟容器Cat-普通服务的注册和提取(源代码

[202]模拟容器Cat-针对泛型服务类型的支持(源代码

[203]模拟容器Cat-为同一类型提供多个服务注册(源代码

[204]模拟容器Cat-服务实例的生命周期(源代码

[201]模拟容器Cat-普通服务的注册和提取

我们定义了如下所示的接口和对应的实现类型来演示针对Cat的服务注册。Foo、Bar、Baz和Qux分别实现了对应的接口IFoo、IBar、IBaz和IQux,其中Qux类型上标注的MapToAttribute特性注册了与对应接口IQux之间的映射。四个类型派生于的基类Base实现了IDisposable接口,我们在其构造函数和实现的Dispose方法中输出相应的文本,以确定对应的实例何时被创建和释放。我们还定义了一个泛型的接口IFoobar<T1, T2>和对应的实现类Foobar<T1, T2>,用来演示Cat针对泛型服务实例的提供。

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 T1 Foo { get; }
public T2 Bar { get; }
public Foobar(T1 foo, T2 bar)
{
Foo = foo;
Bar = bar;
}
}

Lifetime是一个代表服务实例生命周期的枚举,它代表的三种生命周期模式定义如下。

public enum Lifetime
{
Root,
Self,
Transient
}

如下所示的代码片段创建了一个Cat对象,并采用上面提到的方式针对接口IFoo、IBar和IBaz注册了对应的服务,它们采用的生命周期模式分别为Transient、Self和Root。另外,我们还调用了另一个将当前入口程序集作为参数的Register方法,该方法会解析指定程序集中标注了MapToAttribute特性的类型并进行批量服务注册。对于我们演示的程序来说,该方法会完成针对IQux/Qux类型的服务注册。接下来我们利用Cat对象创建了它的两个子容器,并调用子容器的GetService<T>方法来提供相应的服务实例。

using App;

var root = new Cat()
.Register<IFoo, Foo>(Lifetime.Transient)
.Register<IBar>(_ => new Bar(), Lifetime.Self)
.Register<IBaz, Baz>(Lifetime.Root)
.Register(typeof(Foo).Assembly);
var cat1 = root.CreateChild();
var cat2 = root.CreateChild(); void GetServices<TService>(Cat cat) where TService : class
{
cat.GetService<TService>();
cat.GetService<TService>();
} GetServices<IFoo>(cat1);
GetServices<IBar>(cat1);
GetServices<IBaz>(cat1);
GetServices<IQux>(cat1);
Console.WriteLine();
GetServices<IFoo>(cat2);
GetServices<IBar>(cat2);
GetServices<IBaz>(cat2);
GetServices<IQux>(cat2);

上面的程序运行之后会在控制台上输出图1所示的结果。由于服务IFoo被注册为Transient服务,所以Cat针对四次请求都会创建一个全新的Foo对象。IBar服务的生命周期模式为Self,对于同一个Cat只会创建一个Bar对象,所以整个过程中会创建两个Bar对象。IBaz和IQux服务采用Root生命周期,所以同根的两个Cat对象提供的其实是同一个Baz/Qux对象。

图1Cat按照服务注册对应的生命周期模式提供服务实例

[202]模拟容器Cat-针对泛型服务类型的支持

Cat同样可以提供泛型服务实例。如下面的代码片段所示,在为创建的Cat对象添加了针对IFoo和IBar接口的服务注册之后,我们调用Register方法注册了针对泛型定义IFoobar<,>的服务注册,具体的实现类型为Foobar<,>。当我们利用Cat对象提供一个类型为IFoobar<IFoo, IBar>的服务实例时,它会创建并返回一个Foobar<Foo, Bar>对象。

using App;
using System.Diagnostics; var cat = new Cat()
.Register<IFoo, Foo>(Lifetime.Transient)
.Register<IBar, Bar>(Lifetime.Transient)
.Register(typeof(IFoobar<,>), typeof(Foobar<,>), Lifetime.Transient); var foobar = (Foobar<IFoo, IBar>?)cat.GetService<IFoobar<IFoo, IBar>>();
Debug.Assert(foobar?.Foo is Foo);
Debug.Assert(foobar?.Bar is Bar);

[203]模拟容器Cat-为同一类型提供多个服务注册

我们可以为同一个类型提供多个服务注册。虽然添加的所有服务注册均是有效的,但由于GetService<TService>扩展方法总是返回一个服务实例,我们对该方法应用了“后来居上”的策略,即采用最近添加的服务注册创建服务实例。另一个GetServices<TService>扩展方法将返回根据所有服务注册提供的服务实例。下面的代码片段为创建的Cat对象添加了三个针对Base类型的服务注册,对应的实现类型分别为Foo、Bar和Baz。我们调用了Cat对象的GetServices<Base>方法返回包含三个Base对象的集合,集合元素的类型分别为Foo、Bar和Baz。

using App;
using System.Diagnostics; var services = new Cat()
.Register<Base, Foo>(Lifetime.Transient)
.Register<Base, Bar>(Lifetime.Transient)
.Register<Base, Baz>(Lifetime.Transient)
.GetServices<Base>();
Debug.Assert(services.OfType<Foo>().Any());
Debug.Assert(services.OfType<Bar>().Any());
Debug.Assert(services.OfType<Baz>().Any());

[204]模拟容器Cat-服务实例的生命周期

如果提供服务实例的类型实现了IDisposable接口,我们必须在适当的时候调用其Dispose方法释放它。由于服务实例的生命周期完全由作为依赖注入容器的Cat对象来管理,所以通过调用Dispose方法针对服务实例的释放也由它负责。Cat对象针对提供服务实例的释放策略取决于采用的生命周期模式,具体的策略如下。

  • TransientSelf:所有实现了IDisposable接口的服务实例会被当前Cat对象保存起来,当Cat对象自身的Dispose方法被调用的时候,这些服务实例的Dispose方法会随之被调用。
  • Root:由于服务实例保存在作为根容器的Cat对象上,所以当作为根的Cat对象的Dispose方法被调用的时候,这些服务实例的Dispose方法会随之被调用。

上述释放策略可以通过如下演示实例来印证。如下代码片段所示,我们创建了一个Cat对象并添加了相应的服务注册。我们调用它的CreateChild方法创建了代表子容器的Cat对象,并用它提供了四个注册服务对应的实例。

using App;
using (var root = new Cat()
.Register<IFoo, Foo>(Lifetime.Transient)
.Register<IBar>(_ => new Bar(), Lifetime.Self)
.Register<IBaz, Baz>(Lifetime.Root)
.Register(typeof(IFoo).Assembly))
{
using (var cat = root.CreateChild())
{
cat.GetService<IFoo>();
cat.GetService<IBar>();
cat.GetService<IBaz>();
cat.GetService<IQux>();
Console.WriteLine("Child cat is disposed.");
}
Console.WriteLine("Root cat is disposed.");
}

由于两个Cat对象的创建都是在using块中进行的,所以它们的Dispose方法都会在using块结束的地方被调用。该程序运行之后会在控制台上输出图2所示的结果,我们可以看到当作为子容器的Cat对象的Dispose方法被调用时,由它提供的两个生命周期模式分别为Transient和Self的服务实例(Foo和Bar)被正常释放。而生命周期模式为Root的服务实例(Baz和Qux对象)的Dispose方法会延迟到作为根容器的Cat对象的Dispose方法被调用的时候。


图2 服务实例的释放

ASP.NET Core 6框架揭秘实例演示[04]:自定义依赖注入框架的更多相关文章

  1. ASP.NET Core 6框架揭秘实例演示[06]:依赖注入框架设计细节

    由于依赖注入具有举足轻重的作用,所以<ASP.NET Core 6框架揭秘>的绝大部分章节都会涉及这一主题.本书第3章对.NET原生的依赖注入框架的设计和实现进行了系统的介绍,其中设计一些 ...

  2. ASP.NET Core 6框架揭秘实例演示[05]:依赖注入基本编程模式

    毫不夸张地说,整个ASP.NET Core就是建立在依赖注入框架之上的.ASP.NET Core应用在启动时构建管道所需的服务,以及管道处理请求使用到的服务,均来源于依赖注入容器.依赖注入容器不仅为A ...

  3. ASP.NET Core 6框架揭秘实例演示[07]:文件系统

    ASP.NET Core应用具有很多读取文件的场景,如读取配置文件.静态Web资源文件(如CSS.JavaScript和图片文件等).MVC应用的视图文件,以及直接编译到程序集中的内嵌资源文件.这些文 ...

  4. ASP.NET Core 6框架揭秘实例演示[08]:配置的基本编程模式

    .NET的配置支持多样化的数据源,我们可以采用内存的变量.环境变量.命令行参数.以及各种格式的配置文件作为配置的数据来源.在对配置系统进行系统介绍之前,我们通过几个简单的实例演示一下如何将具有不同来源 ...

  5. ASP.NET Core 6框架揭秘实例演示[09]:配置绑定

    我们倾向于将IConfiguration对象转换成一个具体的对象,以面向对象的方式来使用配置,我们将这个转换过程称为配置绑定.除了将配置树叶子节点配置节的绑定为某种标量对象外,我们还可以直接将一个配置 ...

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

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

  7. ASP.NET Core 6框架揭秘实例演示[11]:诊断跟踪的几种基本编程方式

    在整个软件开发维护生命周期内,最难的不是如何将软件系统开发出来,而是在系统上线之后及时解决遇到的问题.一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根 ...

  8. ASP.NET Core 6框架揭秘实例演示[12]:诊断跟踪的进阶用法

    一个好的程序员能够在系统出现问题之后马上定位错误的根源并找到正确的解决方案,一个更好的程序员能够根据当前的运行状态预知未来可能发生的问题,并将问题扼杀在摇篮中.诊断跟踪能够帮助我们有效地纠错和排错&l ...

  9. ASP.NET Core 6框架揭秘实例演示[13]:日志的基本编程模式[上篇]

    <诊断跟踪的几种基本编程方式>介绍了四种常用的诊断日志框架.其实除了微软提供的这些日志框架,还有很多第三方日志框架可供我们选择,比如Log4Net.NLog和Serilog 等.虽然这些框 ...

随机推荐

  1. zabbix监控图形中文乱码的解决方法

    问题描述: 最近搭建了一套zabbix,当我把语言切换到中文的时候,发现监控的图形界面中一些中文参数乱码,但是图形界面在英文环境下完全没有乱码问题.如下图(中文界面): 解决方法: 解决方法有两种,方 ...

  2. 日K蜡烛图

    股票价格涨跌趋势,常用蜡烛图技术中的K线图来表示,分为按日的日K线.按周的周K线.按月的月K线等.以日K线为例,每天股票价格从开盘到收盘走完一天,对应一根蜡烛小图,要表示四个价格:开盘价格Open(早 ...

  3. Android函数抽取壳的实现

    0x0 前言 函数抽取壳这个词不知道从哪起源的,但我理解的函数抽取壳是那种将dex文件中的函数代码给nop,然后在运行时再把字节码给填回dex的这么一种壳. 函数抽取前: 函数抽取后: 很早之前就想写 ...

  4. Win7升级Win11升级记录及教程 【错误码(0×8004242d)】

    hellow,大家好,我是公众号棱镜Prism K的[K君].家中电脑因为一些原因不得不进行升级,下面是我对这次电脑升级所进行的记录. step 1.打开微软官网,找到对应的WIN11下载模块,这里注 ...

  5. Git:解决报错:fatal: The remote end hung up unexpectedly

    使用全局代理即可.字面意思连接时间过长,被github中断了连接.

  6. 使用VS Code的MySQL扩展管理数据库

    我将在本文告诉你如何用VS Code的扩展程序管理MySQL数据库,包括连接到MySQL.新建数据库和表.修改字段定义.简单的查询方法以及导入导出. 在许多情况下,我们需要随时查看数据库的记录来确保程 ...

  7. C#8.0 可空引用类型

    介绍 我们的项目代码运行时最频繁的错误之一就是 System.NullReferenceException 异常,c#8.0增加的可为空引用类型就是用来帮助开发者降低甚至消除NULL异常.我们需要注意 ...

  8. gin框架中全局跨域请求处理设置

    跨域访问的问题 OPTIONS请求 全局跨域访问中间件 // 跨域访问:cross origin resource share func Cors() gin.HandlerFunc { return ...

  9. go get失败解决办法

    go get时由于防火墙的原因,会导致失败.目前可以通过修改GOPROXY的方法解决该问题. 无论是在win下还是linux,macos下,只需要将环境变量GOPROXY设置成https://gopr ...

  10. 用 CSS 让你的文字更有文艺范

    透明文字,模糊文字,镂空文字,渐变文字,图片背景文字,用 CSS 让你的文字也有 freestyle- 前言 我们做页面涉及字体的时候,最多就是换个 color 换个 font-family,总是觉得 ...