来源: 知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案二 – 初久的私房菜 – 博客园
教程
01 | 模块化方案一
02 | 模块化方案二
其他教程预览
分库分表项目实战教程
01 | 前言
02 | 简单的分库分表设计
03 | 控制反转搭配简单业务
04 | 强化设计方案
05 | 完善业务自动创建数据库
06 | 最终篇-通过AOP自动连接数据库-完成日志业务
简介
开讲第二篇,本篇代码并非Copy的ABP,只是参考ABP的功能,进行的实现方案,让代码更加通俗易懂。代码的讲解思路和上一篇一样,但是不引用上篇的写法。
开始
第一步 基本操作
还是老样子,我们新建一个模块化接口类
新建接口 IAppModule (ps:项目中起的类名和方法名尽量对标ABP)
public interface IAppModule
{
void OnPreConfigureServices();
void OnConfigureServices();
void OnPostConfigureServices();
void OnPreApplicationInitialization();
void OnApplicationInitialization();
void OnPostApplicationInitialization();
void OnApplicationShutdown();
}
新建类 AppModule 继承 IAppModule
public abstract class AppModule : IAppModule
{
public virtual void OnPreConfigureServices()
{
}
public virtual void OnConfigureServices()
{
}
public virtual void OnPostConfigureServices()
{
}
public virtual void OnPreApplicationInitialization()
{
}
public virtual void OnApplicationInitialization()
{
}
public virtual void OnPostApplicationInitialization()
{
}
public virtual void OnApplicationShutdown()
{
}
}
第二步 预准备
这一步来完成ABP的DependsOnAttribute,通过特性进行引入模块,
这里参数 params Type[] 因为一个模块会依赖多个模块
新建类 DependsOnAttribute 继承 Attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : Attribute
{
public Type[] DependModuleTypes { get; private set; }
public DependsOnAttribute(params Type[] dependModuleTypes)
{
DependModuleTypes = dependModuleTypes ?? new Type[0];
}
}
既然一个模块会包含多个模块的引用,那么就应该有一个存储的方式
新建类 ModuleDescriptor 该类来存储 自身和引用的其他模块
public class ModuleDescriptor
{
private object _instance;
public Type ModuleType { get; private set; }
public ModuleDescriptor[] Dependencies { get; private set; }
public object Instance
{
get
{
if (this._instance == null)
{
this._instance = Activator.CreateInstance(this.ModuleType);
}
return this._instance;
}
}
public ModuleDescriptor(Type moduleType, params ModuleDescriptor[] dependencies)
{
this.ModuleType = moduleType;
this.Dependencies = dependencies ?? new ModuleDescriptor[0];
}
}
第三步 模块管理器
来到核心步骤,这里我们写模块管理器,白话就是存储模块和模块操作方法的一个类(同上一篇的StartupModulesOptions)
第一步肯定是模块的启动
我们新建 IModuleManager接口
public interface IModuleManager : IDisposable
{
void StartModule<TModule>(IServiceCollection services)
where TModule : IAppModule;
}
紧跟新建类 ModuleManager 继承 IModuleManager, StartModule 先放在一边
这里的思路是:模块是从一个入口的根模块开始的慢慢的形成一个树状的引用关系,我们首先需要拿到所有的模块引用,并把他们从树叶为起点排列起来,依次注入。
(理解为A=>B=>C 那么注入的顺序应该是 C=>B=>A)
1.先来实现根绝入口递归获取所有的引用关系 我已经在方法中将每一步的注释都写上了
protected virtual List<ModuleDescriptor> VisitModule(Type moduleType) {
var moduleDescriptors = new List<ModuleDescriptor>();
if (moduleType.IsAbstract || moduleType.IsInterface || moduleType.IsGenericType || !moduleType.IsClass) {
return moduleDescriptors;
}
var baseInterfaceType = moduleType.GetInterface(_moduleInterfaceTypeFullName, false);
if (baseInterfaceType == null)
{
return moduleDescriptors;
}
var dependModulesAttribute = moduleType.GetCustomAttribute<DependsOnAttribute>();
if (dependModulesAttribute == null)
{
moduleDescriptors.Add(new ModuleDescriptor(moduleType));
}
else {
var dependModuleDescriptors = new List<ModuleDescriptor>();
foreach (var dependModuleType in dependModulesAttribute.DependModuleTypes)
{
dependModuleDescriptors.AddRange(
VisitModule(dependModuleType)
);
}
moduleDescriptors.Add(new ModuleDescriptor(moduleType, dependModuleDescriptors.ToArray()));
}
return moduleDescriptors;
}
补: _moduleInterfaceTypeFullName 定义
public static string _moduleInterfaceTypeFullName = typeof(IAppModule).FullName;
新建类 Topological 这块没啥特别要讲的根据链接去看下就好了
public static class Topological
{
public static List<T> Sort<T>(IEnumerable<T> source, Func<T, IEnumerable<T>> getDependencies) {
var sorted = new List<T>();
var visited = new Dictionary<T, bool>();
foreach (var item in source)
{
Visit(item, getDependencies, sorted, visited);
}
return sorted;
}
static void Visit<T>(T item, Func<T, IEnumerable<T>> getDependencies, List<T> sorted, Dictionary<T, bool> visited)
{
bool inProcess;
var alreadyVisited = visited.TryGetValue(item, out inProcess);
if (alreadyVisited)
{
if (inProcess)
{
throw new ArgumentException("模块出现循环依赖.");
}
}
else
{
visited[item] = true;
var dependencies = getDependencies(item);
if (dependencies != null)
{
foreach (var dependency in dependencies)
{
Visit(dependency, getDependencies, sorted, visited);
}
}
visited[item] = false;
sorted.Add(item);
}
}
}
回到 ModuleManager 新建方法 ModuleSort
public virtual List<ModuleDescriptor> ModuleSort<TModule>() where TModule : IAppModule
{
var moduleDescriptors = VisitModule(typeof(TModule));
return Topological.Sort(moduleDescriptors, o => o.Dependencies);
}
补:ModuleSort本来是个私有方法 后为了让模块使用者可以实现重写,请在 IModuleManager 加入
List<ModuleDescriptor> ModuleSort<TModule>()
where TModule : IAppModule;
3.模块已经可以通过方法拿到了就来实现 StartModule 方法 筛选去重 依次进行注入, 并最终保存到全局对象中
public virtual IReadOnlyList<ModuleDescriptor> ModuleDescriptors { get; protected set; }
public void StartModule<TModule>(IServiceCollection services) where TModule : IAppModule
{
var moduleDescriptors = new List<ModuleDescriptor>();
var moduleDescriptorList = this.ModuleSort<TModule>();
foreach (var item in moduleDescriptorList)
{
if (moduleDescriptors.Any(o => o.ModuleType.FullName == item.ModuleType.FullName))
{
continue;
}
moduleDescriptors.Add(item);
services.AddSingleton(item.ModuleType, item.Instance);
}
ModuleDescriptors = moduleDescriptors.AsReadOnly();
}
4.ModuleDescriptors既然存储着我们的所有模块,那么我们怎么执行模块的方法呢
入口通过调用下面的方法进行模块的方法调用
public IServiceCollection ConfigurationService(IServiceCollection services, IConfiguration configuration) {
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPreConfigureServices();
}
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnConfigureServices();
}
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPostConfigureServices();
}
return services;
}
public IServiceProvider ApplicationInitialization(IServiceProvider serviceProvider)
{
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPreApplicationInitialization();
}
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnApplicationInitialization();
}
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnPostApplicationInitialization();
}
return serviceProvider;
}
public void ApplicationShutdown()
{
foreach (var module in ModuleDescriptors)
{
(module.Instance as IAppModule)?.OnApplicationShutdown();
}
}
当然还漏了一个模块销毁,该方法在主模块被销毁的时候调用(ps: 我个人思路应该是从树叶开始进行,但是ABP对模块顺序进行了反转从根开始进行销毁,所以这里同上)
public void Dispose()
{
this.Dispose(true);
}
protected virtual void Dispose(bool state)
{
this.ApplicationShutdown();
}
第四步 Extensions
模块管理器写完了,那么这个方法如何调用呢来写我们的 Extensions
新建 RivenModuleServiceCollectionExtensions 类,让其完成ConfigurationService方法的模块调用
public static class RivenModuleServiceCollectionExtensions
{
public static IServiceCollection AddRivenModule<TModule>(this IServiceCollection services, IConfiguration configuration)
where TModule : IAppModule
{
var moduleManager = new ModuleManager();
moduleManager.StartModule<TModule>(services);
moduleManager.ConfigurationService(services, configuration);
services.TryAddSingleton<IModuleManager>(moduleManager);
return services;
}
}
新建 RivenModuleIApplicationBuilderExtensions 类 ,让其完成Configuration方法的模块调用
public static class RivenModuleIApplicationBuilderExtensions
{
public static IServiceProvider UseRivenModule(this IServiceProvider serviceProvider)
{
var moduleManager = serviceProvider.GetService<IModuleManager>();
return moduleManager.ApplicationInitialization(serviceProvider);
}
}
第五步 测试
新建一个测试项目,引入写好的模块化类库,在 ConfigureServices 中调用
services.AddRivenModule<MyAppStartupModule>(Configuration);
Configure 中调用
app.ApplicationServices.UseRivenModule();
模块销毁演示(ps:这个是演示效果、实际是在项目停止的时候进行。)
app.Map("/ApplicationShutdown", _ =>
{
_.Run((context) =>
{
var moduleManager = app.ApplicationServices.GetService<IModuleManager>();
moduleManager.ApplicationShutdown();
return Task.FromResult(0);
});
});
补:
新建 MyAppStartupModule、TestModuleA、TestModuleB 继承AppModule。
MyAppStartupModule作为入口模块 引用 A => B 然后在模块方法中打印 Console.WriteLine 看效果
补充 给模块传递参数
新建 ApplicationInitializationContext 类
public class ApplicationInitializationContext
{
public IServiceProvider ServiceProvider { get; }
public IConfiguration Configuration { get; }
public ApplicationInitializationContext([NotNull] IServiceProvider serviceProvider, [NotNull] IConfiguration configuration)
{
ServiceProvider = serviceProvider;
Configuration = configuration;
}
}
新建 ApplicationShutdownContext 类
public class ApplicationShutdownContext
{
public IServiceProvider ServiceProvider { get; }
public ApplicationShutdownContext([NotNull] IServiceProvider serviceProvider)
{
ServiceProvider = serviceProvider;
}
}
新建 ServiceConfigurationContext 类
public class ServiceConfigurationContext
{
public IServiceCollection Services { get; protected set; }
public IConfiguration Configuration { get; protected set; }
public ServiceConfigurationContext(IServiceCollection services, IConfiguration configuration)
{
Services = services;
Configuration = configuration;
}
}
修改 IAppModule 接口, 模块和实现都自己手动都同步一下
public interface IAppModule
{
void OnPreConfigureServices(ServiceConfigurationContext context);
void OnConfigureServices(ServiceConfigurationContext context);
void OnPostConfigureServices(ServiceConfigurationContext context);
void OnPreApplicationInitialization(ApplicationInitializationContext context);
void OnApplicationInitialization(ApplicationInitializationContext context);
void OnPostApplicationInitialization(ApplicationInitializationContext context);
void OnApplicationShutdown(ApplicationShutdownContext context);
}
修改 ModuleManager的 ConfigurationService、ApplicationInitialization、ApplicationShutdown 方法给调用传递对应参数
这部分代码我就不贴了,会的大佬都能自己写,想看的去我的github直接下载源码看吧,麻烦老板们给点个星星!!!
项目地址
鸣谢