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


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

    [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对象。

    image

    图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方法被调用的时候。

    image
    图2 服务实例的释放

  • 相关阅读:
    Linux的shell编程易错点解析(cp复制带空格和短横线的文件)
    企业为什么会找工业设计公司解决问题
    【Linux】NFS服务器搭建配置挂载(Linux挂载Windows目录)
    bindParam() 和 bindValue() 的区别
    浏览器页面刷新,history增加,需要多次调用history.back()才能后退的解决方法
    【CT】LeetCode手撕—415. 字符串相加
    进程
    如何操作(增、删、改、查)常见的 HTML 元素呢?(包含原生 js 和 JQuery 语法对照)
    Oracle EBS Interface/API(42) -会计账户组合交叉验证规则标准API和客制化API
    【死磕JVM】用Arthas排查JVM内存 真爽!我从小用到大
  • 原文地址:https://www.cnblogs.com/artech/p/inside-asp-net-core-6-4.html