实现模块化注册

.Net Core实现模块化批量注入

我将新建一个项目从头开始项目名称Sukt.Core.

该项目分层如下:

  • Sukt.Core.API

    为前端提供APi接口(里面尽量不存在业务逻辑,仅能引用应用层,不可跨层引用)

  • Sukt.Core.Application 应用层实现(主要存放业务逻辑,以及数据持久层调用)

  • Sukt.Core.Application.Contracts 应用契约层(存放应用层的接口定义)

  • Sukt.Core.Test 单元测试层

  • Sukt.Core.Dtos MVVM层,(用于存放与前端交互模型,尽量避免前端直接调用实体模型)

  • Sukt.Core.EntityFrameworkCore 数据持久化层,用来存放调用数据库实现

  • Sukt.Core.Shared (本层最大,本层可以被任意层引用,本层包括了一些自定义扩展)[基础模块注册放到本层/需要自定义扩展模块需要引用本层的SuktAppModuleBase基础类 ]

  • 基于官方DI实现模块化批量自动注入

    阶段一

    我们都知道官方的DI没有批量注入;那么我们第一阶段的目标就是来实现这个功能;在实现这个功能前;我们先来讲一下模块化;我们的标题就是实现模块化注册;

    我们都知道abp框架他的框架在Startup中有很少的代码;可以说几乎没有;那么他是怎么做到的呢?

    他是将需要在Startup中需要都封装成了一个个模块;需要哪些直接去引用这些模块就行了;简化了Startup中的代码;如果添加了某个模块代码报错了只需要删除这个模块来确定错误原因,这样很快就能找到问题所在;不需要像之前一样一个个去注释这些服务的注册;来确定问题原因;这时候有人说了;我直接写静态扩展不就好了嘛,此处不抬杠,每个人有个人的编码爱好;本项目只讲知识点。

    第一步我们要了解如何实现批量注入的原理;这个原理就是利用反射原理;有人说反射性能慢,但是反射能做的东西有很多;例如反射获取这个类的特性、父类、接口等等;

    我们都知道依赖注入有三种生命周期分别是:Singleton(单例)、Scoped(作用域)和Transient(瞬时)

    本文我们只讲解Sukt.Core.Shared层的实现功能以及简单原理;(讲真我理论知识也不好半吊子,以后大家多多带带我;开个玩笑);到此结束;

    • 进入正文

    我们都知道批量注入实现的原理其实就是获取程序集然后通过反射来进行注入;那么我们的程序在进行开发的时候需要引用一些第三方的程序集这时候我们需要把这些程序集过滤掉我们来看代码(一下代码就是获取程序集的)

    public static class AssemblyHelper
    {
    /// <summary>
    /// 获取项目程序集,排除所有的系统程序集(Microsoft.***、System.***等)、Nuget下载包
    /// </summary>
    /// <returns></returns>
    private static IList<Assembly> GetAllAssemblies()
    {

    string[] filters =
    {
    "mscorlib",
    "netstandard",
    "dotnet",
    "api-ms-win-core",
    "runtime.",
    "System",
    "Microsoft",
    "Window",
    };
    List<Assembly> list = new List<Assembly>();
    var deps = DependencyContext.Default;
    //排除所有的系统程序集、Nuget下载包
    var libs = deps.CompileLibraries.Where(lib => !lib.Serviceable && lib.Type != "package" && !filters.Any(lib.Name.StartsWith));

    try
    {
    foreach (var lib in libs)
    {
    var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(lib.Name));
    list.Add(assembly);
    }
    }
    catch (Exception ex)
    {

    throw ex;
    }

    return list;
    }

    public static Assembly[] FindAllItems()
    {
    return GetAllAssemblies().ToArray();
    }
    }

    阶段二 创建基础模块

    我们的基础模块有三部分组成

    接口:ISuktAppModuleManager、实现:SuktAppModuleManager、服务基类:SuktAppModuleBase

    SuktAppModuleBase我们定义成为抽象类里面包含两个方法分别是之前在Startup中看到的ConfigureServices和Configure;其中Configure方法传递的参数和Startup略有不同我们看代码

    /// <summary>
    /// start服务模块基类
    /// </summary>
    public abstract class SuktAppModuleBase
    {
    /// <summary>
    /// 将模块服务添加到依赖注入服务容器中
    /// </summary>
    /// <param name="service">依赖注入服务容器</param>
    /// <returns></returns>
    public virtual IServiceCollection ConfigureServices(IServiceCollection service)
    {
    return service;
    }
    /// <summary>
    /// Http管道方法由运行时调用;使用此方法配置HTTP请求管道。
    /// </summary>
    public virtual void Configure(IApplicationBuilder applicationBuilder)
    {

    }
    }

    ISuktAppModuleManager和SuktAppModuleManager是模块管理中心接口包含两个方法一个属性分别是:SuktSourceModules属性;LoadModules和Configure方法:以下是两个方法的代码:

    /// <summary>
    /// SuktAppModule管理实现
    /// </summary>
    public class SuktAppModuleManager : ISuktAppModuleManager
    {
    public List<SuktAppModuleBase> SuktSourceModules { get; private set; }
    public SuktAppModuleManager()
    {
    SuktSourceModules = new List<SuktAppModuleBase>();
    }
    public IServiceCollection LoadModules(IServiceCollection services)
    {
    var typeFinder = services.GetOrAddSingletonService<ITypeFinder, TypeFinder>();
    var baseType = typeof(SuktAppModuleBase);//反射基类模块
    var moduleTypes = typeFinder.Find(x => x.IsSubclassOf(baseType) && !x.IsAbstract).Distinct().ToArray();///拿到SuktAppModuleBase的所有派生类并且不是抽象类
    if(moduleTypes?.Count()<=0)
    {
    throw new SuktAppException("需要加载的模块未找到!!!");
    }
    SuktSourceModules.Clear();
    var moduleBases = moduleTypes.Select(m => (SuktAppModuleBase)Activator.CreateInstance(m));
    SuktSourceModules.AddRange(moduleBases);
    List<SuktAppModuleBase> modules = SuktSourceModules.ToList();
    foreach (var module in modules)
    {
    services = module.ConfigureServices(services);
    }
    return services;
    }
    public void Configure(IApplicationBuilder applicationBuilder)
    {
    foreach (var module in SuktSourceModules)
    {
    module.Configure(applicationBuilder);
    }
    }
    }

    这些都是前期准备工作,下面我们进入正文,其中有些方法或者特性是我自己扩展的就不一一类据了,毕竟代码太多了如果有需要敬请去Github下载源代码;

    阶段三 模块化批量自动注入

    我们先创建一个DependencyAppModule类,继承与SuktAppModuleBase基类;

    前面我们讲了我们有一个SuktAppModuleBase的基类;那么我们在扩展自动注入的时候就要用到这个基类里面的方法了,如果我们不继承这个基类也可以实现但是那种就不是我们想要的模块化注册了,所以我们需要继承这个基类,首先我们都知道基类里面其实包括之前在Startup看到的两个方法那么我们的DependencyAppModule类需要重写里面的ConfigureServices方法;下面是代码实现

    /// <summary>
    /// 自动注入模块,继承与SuktAppModuleBase类进行实现
    /// </summary>
    public class DependencyAppModule:SuktAppModuleBase
    {
    public override IServiceCollection ConfigureServices(IServiceCollection service)
    {
    SuktIocManage.Instance.SetServiceCollection(service);//写入服务集合
    this.BulkIntoServices(service);
    return service;
    }
    /// <summary>
    /// 批量注入服务
    /// </summary>
    /// <param name="services"></param>
    private void BulkIntoServices(IServiceCollection services)
    {
    var typeFinder = services.GetOrAddSingletonService<ITypeFinder, TypeFinder>();
    typeFinder.NotNull(nameof(typeFinder));
    Type[] dependencyTypes = typeFinder.Find(type => type.IsClass && !type.IsAbstract && !type.IsInterface && type.HasAttribute<DependencyAttribute>());
    foreach (var dependencyType in dependencyTypes)
    {
    AddToServices(services, dependencyType);
    }
    }
    /// <summary>
    /// 将服务实现类型注册到服务集合中
    /// </summary>
    /// <param name="services">服务集合</param>
    /// <param name="implementationType">要注册的服务实例类型</param>
    protected virtual void AddToServices(IServiceCollection services, Type implementationType)
    {
    var atrr = implementationType.GetAttribute<DependencyAttribute>();
    Type[] serviceTypes = implementationType.GetImplementedInterfaces().Where(o => !o.HasAttribute<IgnoreDependencyAttribute>()).ToArray();
    if (serviceTypes.Length == 0)
    {
    services.TryAdd(new ServiceDescriptor(implementationType, implementationType, atrr.Lifetime));
    return;
    }
    if (atrr?.AddSelf == true)
    {
    services.TryAdd(new ServiceDescriptor(implementationType, implementationType, atrr.Lifetime));
    }
    foreach (var interfaceType in serviceTypes)
    {
    services.Add(new ServiceDescriptor(interfaceType, implementationType, atrr.Lifetime));
    }
    }
    }

    在这里我们看到了另一个类SuktIocManage那么我们这个类是干嘛的呢?其实这个很简单我们只需要看一下里面的代码就好:看到类的名称我们就能想象到了;IocManage 就是容器管理嘛,我们在这里其实是为了获取服务;有些地方用不了注入就需要用到这个服务了。

    /// <summary>
    /// IOC管理
    /// </summary>
    public class SuktIocManage
    {
    /// <summary>
    /// 服务提供者
    /// </summary>
    private IServiceProvider _provider;
    /// <summary>
    /// 服务集合
    /// </summary>
    private IServiceCollection _services;
    /// <summary>
    /// 创建懒加载Ioc管理实例
    /// </summary>
    private static readonly Lazy<SuktIocManage> SuktInstanceLazy = new Lazy<SuktIocManage>(() => new SuktIocManage());
    /// <summary>
    /// 构造方法
    /// </summary>
    private SuktIocManage()
    {
    }
    public static SuktIocManage Instance => SuktInstanceLazy.Value;
    /// <summary>
    /// 设置应用程序服务提供者
    /// </summary>
    internal void SetApplicationServiceProvider(IServiceProvider provider)
    {
    _provider.NotNull(nameof(provider));
    _provider = provider;
    }
    /// <summary>
    /// 设置应用程序服务集合
    /// </summary>
    internal void SetServiceCollection(IServiceCollection services)
    {
    services.NotNull(nameof(services));
    _services = services;
    }
    }

    到这里我们都看到了我再反射或程序集的时候用到了两个特性,分别是DependencyAttribute和IgnoreDependencyAttribute

    IgnoreDependencyAttribute特性是不需要注册的服务;

    DependencyAttribute是需要注册的服务;

    我们可以看下这两个特性的代码:

    /// <summary>
    /// 配置此特性将自动进行注入
    /// </summary>
    [AttributeUsage(AttributeTargets.Class)]
    public class DependencyAttribute:Attribute
    {
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="lifetime">注入类型(Scoped\Singleton\Transient)</param>
    public DependencyAttribute(ServiceLifetime lifetime)
    {
    Lifetime = lifetime;
    }
    /// <summary>
    /// 获取 生命周期类型,代替
    /// <see cref="ISingletonDependency"/>,<see cref="IScopeDependency"/>,<see cref="ITransientDependency"/>三个接口的作用
    /// </summary>
    public ServiceLifetime Lifetime { get; }
    /// <summary>
    /// 获取或设置 是否注册自身类型,默认没有接口的类型会注册自身,当此属性值为true时,也会注册自身
    /// </summary>
    public bool AddSelf { get; set; }
    }
    /// <summary>
    /// 配置此特性将忽略依赖注入自动映射
    /// </summary>
    [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]
    public class IgnoreDependencyAttribute:Attribute
    {
    }

    到这里我们的模块化批量自动注入基本上完结了以上是部分代码;整体代码请移步至

    [Github]  https://github.com/GeorGeWzw/Sukt.Core Sukt.Core源码

    那么我们怎么确定我们的服务是否可行呢?这时候我们需要用到单元测试了;

    我们来创建一个单元测试项目Sukt.Core.Test 这个单元测试是Xunit的切忌单元测试需要和sln文件同级,不然会报错;单元测试包括以下几个文件SuktTestServerFixtureBase、SuktWebApplicationFactory和SuktTestStartup

    public class SuktDependencyModuleTest:              IClassFixture<SuktWebApplicationFactory<SuktTestServerFixtureBase>>
    {
    private readonly SuktWebApplicationFactory<SuktTestServerFixtureBase> _factory = null;
    public SuktDependencyModuleTest(SuktWebApplicationFactory<SuktTestServerFixtureBase> factory)
    {
    _factory = factory;
    }
    [Fact]
    public void Test_BulkInjection()
    {
    var provider = _factory.Server.Services;
    var test = provider.GetService<ITestScopedService>();
    Assert.NotNull(test);
    var getTest = test.GetTest();
    Assert.Equal("Test", getTest);
    var testTransientService = provider.GetService<ITestTransientService>();
    Assert.NotNull(testTransientService);
    var transient = testTransientService.GetTransientService();
    Assert.NotNull(transient);
    var testSingleton = provider.GetService<TestSingleton>();
    Assert.NotNull(testSingleton);
    var testService = provider.GetService<ITestService<User>>();
    Assert.NotNull(testService);
    }
    }
    public interface ITestScopedService
    {
    string GetTest();
    }
    [Dependency(ServiceLifetime.Scoped)]
    public class TestScopedService : ITestScopedService
    {
    public string GetTest(){ return "Test";}
    }
    public interface ITestTransientService{string GetTransientService();}
    [Dependency(ServiceLifetime.Transient)]
    public class TestTransientService : ITestTransientService
    {
    public string GetTransientService(){return "测试瞬时注入成功"}
    }
    [Dependency(ServiceLifetime.Singleton)]
    public class TestSingleton
    {
    }
    public interface ITestService<User>
    {
    }
    [Dependency(ServiceLifetime.Scoped)]
    public class TestService : ITestService<User>
    {
    }
    public class User
    {

    }

    好啦,今天这篇开篇文章就先说到这里吧,希望对大家有所帮助。下一章我将来讲Automapper的模块化。

  • 思路来源:感谢大黄瓜;感谢老张的博客指导我入门

从我做起[原生DI实现模块化和批量注入].Net Core 之一的更多相关文章

  1. .Net Core使用Unity替换原生DI

    原文:.Net Core使用Unity替换原生DI 一.DIP.IOC.DI 面对对象设计原则可以帮助我们开发出更好的程序,其中有一个依赖倒置原则DIP并由此引申出IOC.DI等概念.就先粗略的了解一 ...

  2. ASP.NET CORE 学习之原生DI实现批量注册

    以前使用Autofac的时候,只需一句AsImplementInterfaces()就可以很轻松实现批量注册功能.而asp.net core内置的DI框架没有现成的批量注册方法,考虑到替换Autofa ...

  3. .netcore之DI批量注入(支持泛型) - xms

    一旦系统内模块比较多,按DI标准方法去逐个硬敲AddScoped/AddSingleton/AddTransient缺乏灵活性且效率低下,所以批量注入提供了很大的便捷性,特别是对于泛型的服务类,下面介 ...

  4. .NET 使用自带 DI 批量注入服务(Service)和 后台服务(BackgroundService)

    今天教大家如何在asp .net core 和 .net 控制台程序中 批量注入服务和 BackgroundService 后台服务 在默认的 .net 项目中如果我们注入一个服务或者后台服务,常规的 ...

  5. ASP.NET Core 3.0 原生DI拓展实现IocManager

    昨天.NET Core 3.0 正式发布,创建一个项目运行后发现:原来使用的Autofac在ConfigureServices返回IServiceProvider的这种写法已经不再支持.当然Autof ...

  6. 工厂方法模式与IoC/DI控制反转和依赖注入

    IoC——Inversion of Control  控制反转 DI——Dependency Injection   依赖注入 要想理解上面两个概念,就必须搞清楚如下的问题: 参与者都有谁? 依赖:谁 ...

  7. .net core批量注入实现类

    1.获取实现类程序集方法 public class RuntimeHelper { //通过程序集的名称加载程序集 public static Assembly GetAssemblyByName(s ...

  8. DotNetCore依赖注入实现批量注入

    文章转载自平娃子(QQ:273206491):http://os.pingwazi.cn/resource/batchinjectservice 一.依赖注入 通过依赖注入,可以实现接口与实现类的松耦 ...

  9. IOC/DI控制反转与依赖注入

    IOC/DI控制反转与依赖注入 IOC和DI表现的效果的是一样的只不过对于作用的对象不同,有了不一样的名字. 先用一个现实的例子来说明IOC/DI表现出来的效果.

随机推荐

  1. (数据科学学习手札133)利用geopandas绘制拓扑着色地图

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 我们在绘制某些地图时,为了凸显出每个独立的 ...

  2. 《HelloGitHub》第 69 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...

  3. Explain执行计划详解

    一.id id: :表示查询中执行select子句或者操作表的顺序,id的值越大,代表优先级越高,越先执行. id大致会出现 3种情况 二.select_type select_type:表示 sel ...

  4. rsync 守护进程及实时同步

    目录 rsync 守护进程及实时同步 rsync简介 rsync特性 rsync应用场景 cp命令 scp命令 rsync的传输方式 rsync的传输模式 rsync实际使用 rsync命令 案例 r ...

  5. FP增长算法

    Apriori原理:如果某个项集是频繁的,那么它的所有子集都是频繁的. Apriori算法: 1 输入支持度阈值t和数据集 2 生成含有K个元素的项集的候选集(K初始为1) 3 对候选集每个项集,判断 ...

  6. spring5无法在控制台打印日志的原因

    想要在控制台输出spring的日志,却无法输出,log4j2所需要的jar文件都已经导入,log4j2的配置文件也存在,调整日志级别也不行,一通百度后发现是缺少spring的jcl的jar文件,把sp ...

  7. django中使用支付宝

    一.注册 https://auth.alipay.com/login/ant_sso_index.htm?goto=https%3A%2F%2Fopenhome.alipay.com%2Fplatfo ...

  8. Spring cloud 框架 --- Eureka 心得

    spring boot      1.5.9.RELEASE spring cloud    Dalston.SR1 1.前言 (1)接触了spring cloud 框架 ,首先要知道Eureka是什 ...

  9. nuxt2.0项目创建(最新)

     使用import需要babel编译写法如下 //修改1打开package.json文件 "dev": "cross-env NODE_ENV=development n ...

  10. Elasticsearch打造全文搜索引擎(二)

    一.Es的文档.索引的CURD操作 1. elasticsearch概念 集群:一个或多个节点组织在一起 节点:一个节点是集群中的一个服务器,有一个名字来标识,默认是一个随机的漫画角色的名字 分片:将 ...