从我做起[原生DI实现模块化和批量注入].Net Core 之一
实现模块化注册
.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的模块化。
- 思路来源:感谢大黄瓜;感谢老张的博客指导我入门
最新文章
- Python爬虫进阶二之PySpider框架安装配置
- Guava学习笔记(1):Optional优雅的使用null
- Alignment trap 解决方法 【转 结合上一篇
- 金蝶K/3 Cloud 界面解析过程
- C# 溢出检查
- 【hdu5973】高精度威佐夫博弈
- 使用WSAIoctl获取socket扩展函数(如AcceptEx)的指针
- Codeforces Round #Pi (Div. 2) E. President and Roads tarjan+最短路
- 封装的多功能多效果的RecyclerView
- BZOJ 1733: [Usaco2005 feb]Secret Milking Machine 神秘的挤奶机
- c#中的面向对象基础知识总结
- 用Use Case获取需求的方法是否有什么缺陷,还有什么地方需要改进?
- NPOI导入导出Excel工具类
- 网络编程基础【day09】:通过socket实现简单ssh客户端(三)
- Windows下的Anaconda+OpenCV的环境配置
- LintCode: Restore IP Address
- [洛谷P3629] [APIO2010]巡逻
- g2o:一种图优化的C++框架
- Python 安装 django框架
- git 的基本设置以及使用