1. 程式人生 > >知識全聚集 .Net Core 技術突破 | 如何實現一個模組化方案二

知識全聚集 .Net Core 技術突破 | 如何實現一個模組化方案二

### 教程 [01 | 模組化方案一](https://www.cnblogs.com/MrChuJiu/p/13689123.html) [02 | 模組化方案二](https://www.cnblogs.com/MrChuJiu/p/13708035.html) # 其他教程預覽 ### 分庫分表專案實戰教程 ### Git地址: https://github.com/MrChuJiu/EasyLogger [01 | 前言](https://www.cnblogs.com/HDONG/p/13517146.html) [02 | 簡單的分庫分表設計](https://www.cnblogs.com/HDONG/p/13517207.html) [03 | 控制反轉搭配簡單業務](https://www.cnblogs.com/HDONG/p/13527308.html) [04 | 強化設計方案](https://www.cnblogs.com/HDONG/p/13539186.html) [05 | 完善業務自動建立資料庫](https://www.cnblogs.com/HDONG/p/13552014.html) [06 | 最終篇-通過AOP自動連線資料庫-完成日誌業務](https://www.cnblogs.com/HDONG/p/13575511.html) # 簡介 開講第二篇,本篇程式碼並非Copy的ABP,只是參考ABP的功能,進行的實現方案,讓程式碼更加通俗易懂。程式碼的講解思路和上一篇一樣,但是不引用上篇的寫法。 # 開始 ### 第一步 基本操作 還是老樣子,我們新建一個模組化介面類 新建介面 IAppModule (ps:專案中起的類名和方法名儘量對標ABP) ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200918101729.png) ``` /// /// 應用模組介面定義 /// public interface IAppModule { /// /// 配置服務前 /// /// void OnPreConfigureServices(); /// /// 配置服務 /// /// 配置上下文 void OnConfigureServices(); /// /// 配置服務後 /// /// void OnPostConfigureServices(); /// /// 應用啟動前 ///
/// void OnPreApplicationInitialization(); /// /// 應用啟動 /// /// void OnApplicationInitialization(); /// /// 應用啟動後 /// /// void OnPostApplicationInitialization(); /// /// 應用停止 /// /// void OnApplicationShutdown(); } ``` 新建類 AppModule 繼承 IAppModule ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200918101837.png) ``` 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 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200918104236.png) ``` /// /// 模組依賴的模組 ///
[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 該類來儲存 自身和引用的其他模組 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200918102403.png) ``` /// /// 模組描述 ///
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介面 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200918101837.png) ``` public interface IModuleManager : IDisposable { /// /// 啟動模組 /// /// void StartModule(IServiceCollection services) where TModule : IAppModule; } ``` 緊跟新建類 ModuleManager 繼承 IModuleManager, StartModule 先放在一邊 這裡的思路是:模組是從一個入口的根模組開始的慢慢的形成一個樹狀的引用關係,我們首先需要拿到所有的模組引用,並把他們從樹葉為起點排列起來,依次注入。 (理解為A=>B=>C 那麼注入的順序應該是 C=>B=>A) ##### 1.先來實現根絕入口遞迴獲取所有的引用關係 我已經在方法中將每一步的註釋都寫上了 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921181020.png) ``` /// /// 獲取模組依賴樹 /// /// /// protected virtual List VisitModule(Type moduleType) { var moduleDescriptors = new List(); // 是否必須被重寫|是否是介面|是否為泛型型別|是否是一個類或委託 if (moduleType.IsAbstract || moduleType.IsInterface || moduleType.IsGenericType || !moduleType.IsClass) { return moduleDescriptors; } // 過濾沒有實現IRModule介面的類 var baseInterfaceType = moduleType.GetInterface(_moduleInterfaceTypeFullName, false); if (baseInterfaceType == null) { return moduleDescriptors; } // 得到當前模組依賴了那些模組 var dependModulesAttribute = moduleType.GetCustomAttribute(); // 依賴屬性為空 if (dependModulesAttribute == null) { moduleDescriptors.Add(new ModuleDescriptor(moduleType)); } else { // 依賴屬性不為空,遞迴獲取依賴 var dependModuleDescriptors = new List(); 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; ``` ##### 2.拿到依賴關係通過拓撲排序進行順序處理 (ps:拓撲排序地址 https://www.cnblogs.com/myzony/p/9201768.html) 新建類 Topological 這塊沒啥特別要講的根據連結去看下就好了 ``` /// /// 拓撲排序工具類 /// public static class Topological { public static List Sort(IEnumerable source, Func> getDependencies) { var sorted = new List(); var visited = new Dictionary(); foreach (var item in source) { Visit(item, getDependencies, sorted, visited); } return sorted; } static void Visit(T item, Func> getDependencies, List sorted, Dictionary 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); } } // 處理完成置為 false visited[item] = false; sorted.Add(item); } } } ``` 回到 ModuleManager 新建方法 ModuleSort ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921181427.png) ``` /// /// 模組排序 /// /// /// public virtual List ModuleSort() where TModule : IAppModule { // 得到模組樹依賴 var moduleDescriptors = VisitModule(typeof(TModule)); // 因為現在得到的資料是從樹根開始到樹葉 - 實際的注入順序應該是從樹葉開始 所以這裡需要對模組進行排序 return Topological.Sort(moduleDescriptors, o => o.Dependencies); } ``` ##### 補:ModuleSort本來是個私有方法 後為了讓模組使用者可以實現重寫,請在 IModuleManager 加入 ``` /// /// 模組排序 /// /// 啟動模組型別 /// 排序結果 List ModuleSort() where TModule : IAppModule; ``` ##### 3.模組已經可以通過方法拿到了就來實現 StartModule 方法 篩選去重 依次進行注入, 並最終儲存到全域性物件中 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921181804.png) ``` /// /// 模組明細和例項 /// public virtual IReadOnlyList ModuleDescriptors { get; protected set; } /// /// 入口 StartModule /// 我們通過傳遞泛型進來的 TModule 為起點 /// 查詢他的依賴樹 /// /// /// public void StartModule(IServiceCollection services) where TModule : IAppModule { var moduleDescriptors = new List(); var moduleDescriptorList = this.ModuleSort(); // 去除重複的引用 進行注入 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既然儲存著我們的所有模組,那麼我們怎麼執行模組的方法呢 入口通過呼叫下面的方法進行模組的方法呼叫 ``` /// /// 進行模組的 ConfigurationService 方法呼叫 /// /// /// /// 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; } /// /// 進行模組的 Configure 方法呼叫 /// /// /// 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() { // todo我覺得這裡有點問題問 易大師 //var modules = ModuleDescriptors.Reverse().ToList(); 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方法的模組呼叫 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921185426.png) ``` /// /// 模組服務擴充套件 /// public static class RivenModuleServiceCollectionExtensions { /// /// 新增Riven模組服務 /// /// /// /// /// public static IServiceCollection AddRivenModule(this IServiceCollection services, IConfiguration configuration) where TModule : IAppModule { var moduleManager = new ModuleManager(); // 將模組都查詢排序好 moduleManager.StartModule(services); // 呼叫模組 和 子模組的ConfigurationService方法 moduleManager.ConfigurationService(services, configuration); // 注入全域性的 IModuleManager services.TryAddSingleton(moduleManager); return services; } } ``` 新建 RivenModuleIApplicationBuilderExtensions 類 ,讓其完成Configuration方法的模組呼叫 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921185511.png) ``` public static class RivenModuleIApplicationBuilderExtensions { /// /// 使用RivenModule /// /// /// public static IServiceProvider UseRivenModule(this IServiceProvider serviceProvider) { var moduleManager = serviceProvider.GetService(); return moduleManager.ApplicationInitialization(serviceProvider); } } ``` ### 第五步 測試 新建一個測試專案,引入寫好的模組化類庫,在 ConfigureServices 中呼叫 ``` services.AddRivenModule(Configuration); ``` Configure 中呼叫 ``` app.ApplicationServices.UseRivenModule(); ``` 模組銷燬演示(ps:這個是演示效果、實際是在專案停止的時候進行。) ``` app.Map("/ApplicationShutdown", _ => { _.Run((context) => { var moduleManager = app.ApplicationServices.GetService(); moduleManager.ApplicationShutdown(); return Task.FromResult(0); }); }); ``` ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921185836.png) ##### 補: 新建 MyAppStartupModule、TestModuleA、TestModuleB 繼承AppModule。 MyAppStartupModule作為入口模組 引用 A => B 然後在模組方法中列印 Console.WriteLine 看效果 ![](https://git.imweb.io/hdong/ImageBed/raw/master/StartModularImages/QQ20200921190156.png) ### 補充 給模組傳遞引數 新建 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直接下載原始碼看吧,麻煩老闆們給點個星星!!! # 專案地址 ### 知識全聚集,逐個擊破: https://github.com/MrChuJiu/Easy.Core.Flow # 鳴謝 ### [玩雙截棍的熊貓](https://www.cnblogs.com/staneee/) ### 源地址:https://github.com/rivenfx/Modular