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

教程

01 | 模塊化方案一html

02 | 模塊化方案二git

其餘教程預覽

分庫分表項目實戰教程

Git地址: https://github.com/MrChuJiu/EasyLogger

01 | 前言github

02 | 簡單的分庫分表設計數據庫

03 | 控制反轉搭配簡單業務數組

04 | 強化設計方案app

05 | 完善業務自動建立數據庫ide

06 | 最終篇-經過AOP自動鏈接數據庫-完成日誌業務模塊化

簡介

開講第二篇,本篇代碼並不是Copy的ABP,只是參考ABP的功能,進行的實現方案,讓代碼更加通俗易懂。代碼的講解思路和上一篇同樣,可是不引用上篇的寫法。工具

開始

第一步 基本操做

仍是老樣子,咱們新建一個模塊化接口類
新建接口 IAppModule (ps:項目中起的類名和方法名儘可能對標ABP)
測試

/// <summary>
    /// 應用模塊接口定義
    /// </summary>
    public interface IAppModule
    {
        /// <summary>
        /// 配置服務前
        /// </summary>
        /// <param name="context"></param>
        void OnPreConfigureServices();
        /// <summary>
        /// 配置服務
        /// </summary>
        /// <param name="context">配置上下文</param>
        void OnConfigureServices();
        /// <summary>
        /// 配置服務後
        /// </summary>
        /// <param name="context"></param>
        void OnPostConfigureServices();
        /// <summary>
        /// 應用啓動前
        /// </summary>
        /// <param name="context"></param>
        void OnPreApplicationInitialization();
        /// <summary>
        /// 應用啓動
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationInitialization();
        /// <summary>
        /// 應用啓動後
        /// </summary>
        /// <param name="context"></param>
        void OnPostApplicationInitialization();
        /// <summary>
        /// 應用中止
        /// </summary>
        /// <param name="context"></param>
        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

/// <summary>
    /// 模塊依賴的模塊
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class DependsOnAttribute : Attribute
    {
        /// <summary>
        /// 依賴的模塊類型
        /// </summary>
        public Type[] DependModuleTypes { get; private set; }

        public DependsOnAttribute(params Type[] dependModuleTypes)
        {
            DependModuleTypes = dependModuleTypes ?? new Type[0];
        }
    }

既然一個模塊會包含多個模塊的引用,那麼就應該有一個存儲的方式
新建類 ModuleDescriptor 該類來存儲 自身和引用的其餘模塊

/// <summary>
    /// 模塊描述
    /// </summary>
    public class ModuleDescriptor
    {
        private object _instance;

        /// <summary>
        /// 模塊類型
        /// </summary>
        public Type ModuleType { get; private set; }

        /// <summary>
        /// 依賴項
        /// </summary>
        public ModuleDescriptor[] Dependencies { get; private set; }

        /// <summary>
        /// 實例
        /// </summary>
        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
    {
        /// <summary>
        /// 啓動模塊
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        void StartModule<TModule>(IServiceCollection services)
            where TModule : IAppModule;
    }

緊跟新建類 ModuleManager 繼承 IModuleManager, StartModule 先放在一邊
這裏的思路是:模塊是從一個入口的根模塊開始的慢慢的造成一個樹狀的引用關係,咱們首先須要拿到全部的模塊引用,並把他們從樹葉爲起點排列起來,依次注入。
(理解爲A=>B=>C 那麼注入的順序應該是 C=>B=>A)

1.先來實現根絕入口遞歸獲取全部的引用關係 我已經在方法中將每一步的註釋都寫上了

/// <summary>
        /// 獲取模塊依賴樹
        /// </summary>
        /// <param name="moduleType"></param>
        /// <returns></returns>
        protected virtual List<ModuleDescriptor> VisitModule(Type moduleType) {

            var moduleDescriptors = new List<ModuleDescriptor>();
            // 是否必須被重寫|是不是接口|是否爲泛型類型|是不是一個類或委託
            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<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 定義
/// <summary>
        /// 模塊接口類型全名稱
        /// </summary>
        public static string _moduleInterfaceTypeFullName = typeof(IAppModule).FullName;
2.拿到依賴關係經過拓撲排序進行順序處理 (ps:拓撲排序地址 http://www.javashuo.com/article/p-pvxcyudw-hy.html)

新建類 Topological 這塊沒啥特別要講的根據連接去看下就行了

/// <summary>
    /// 拓撲排序工具類
    /// </summary>
    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);
                    }
                }

                // 處理完成置爲 false
                visited[item] = false;
                sorted.Add(item);
            }
        }

    }

回到 ModuleManager 新建方法 ModuleSort

/// <summary>
        /// 模塊排序
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        /// <returns></returns>
        public virtual List<ModuleDescriptor> ModuleSort<TModule>() where TModule : IAppModule
        {
            // 獲得模塊樹依賴
            var moduleDescriptors = VisitModule(typeof(TModule));
            // 由於如今獲得的數據是從樹根開始到樹葉 - 實際的注入順序應該是從樹葉開始 因此這裏須要對模塊進行排序
            return Topological.Sort(moduleDescriptors, o => o.Dependencies);
        }
補:ModuleSort原本是個私有方法 後爲了讓模塊使用者能夠實現重寫,請在 IModuleManager 加入
/// <summary>
        /// 模塊排序
        /// </summary>
        /// <typeparam name="TModule">啓動模塊類型</typeparam>
        /// <returns>排序結果</returns>
        List<ModuleDescriptor> ModuleSort<TModule>()
            where TModule : IAppModule;
3.模塊已經能夠經過方法拿到了就來實現 StartModule 方法 篩選去重 依次進行注入, 並最終保存到全局對象中

/// <summary>
        /// 模塊明細和實例
        /// </summary>
        public virtual IReadOnlyList<ModuleDescriptor> ModuleDescriptors { get; protected set; }

        /// <summary>
        /// 入口 StartModule 
        /// 咱們經過傳遞泛型進來的 TModule 爲起點
        /// 查找他的依賴樹
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        /// <param name="services"></param>
        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既然存儲着咱們的全部模塊,那麼咱們怎麼執行模塊的方法呢

入口經過調用下面的方法進行模塊的方法調用

/// <summary>
        /// 進行模塊的  ConfigurationService 方法調用
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        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;
        }
        /// <summary>
        /// 進行模塊的  Configure 方法調用
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        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;
        }
        /// <summary>
        /// 模塊銷燬
        /// </summary>
        public void ApplicationShutdown()
        {
            // todo我以爲這裏有點問題問 易大師
            //var modules = ModuleDescriptors.Reverse().ToList();

            foreach (var module in ModuleDescriptors)
            {
                (module.Instance as IAppModule)?.OnApplicationShutdown();
            }
        }

固然還漏了一個模塊銷燬,該方法在主模塊被銷燬的時候調用(ps: 我我的思路應該是從樹葉開始進行,可是ABP對模塊順序進行了反轉從根開始進行銷燬,因此這裏同上)

/// <summary>
        /// 主模塊銷燬的時候 銷燬子模塊
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool state)
        {
            this.ApplicationShutdown();

        }

第四步 Extensions

模塊管理器寫完了,那麼這個方法如何調用呢來寫咱們的 Extensions
新建 RivenModuleServiceCollectionExtensions 類,讓其完成ConfigurationService方法的模塊調用

/// <summary>
    /// 模塊服務擴展
    /// </summary>
    public static class RivenModuleServiceCollectionExtensions
    {
        /// <summary>
        /// 添加Riven模塊服務
        /// </summary>
        /// <typeparam name="TModule"></typeparam>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IServiceCollection AddRivenModule<TModule>(this IServiceCollection services, IConfiguration configuration)
            where TModule : IAppModule
        {
            var moduleManager = new ModuleManager();
            // 將模塊都查詢排序好
            moduleManager.StartModule<TModule>(services);
            // 調用模塊 和 子模塊的ConfigurationService方法
            moduleManager.ConfigurationService(services, configuration);
            // 注入全局的  IModuleManager
            services.TryAddSingleton<IModuleManager>(moduleManager);
            return services;
        }
    }

新建 RivenModuleIApplicationBuilderExtensions 類 ,讓其完成Configuration方法的模塊調用

public static class RivenModuleIApplicationBuilderExtensions
    {
        /// <summary>
        /// 使用RivenModule
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <returns></returns>
        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 接口, 模塊和實現都本身手動都同步一下

/// <summary>
    /// 應用模塊接口定義
    /// </summary>
    public interface IAppModule
    {
        /// <summary>
        /// 配置服務前
        /// </summary>
        /// <param name="context"></param>
        void OnPreConfigureServices(ServiceConfigurationContext context);

        /// <summary>
        /// 配置服務
        /// </summary>
        /// <param name="context">配置上下文</param>
        void OnConfigureServices(ServiceConfigurationContext context);

        /// <summary>
        /// 配置服務後
        /// </summary>
        /// <param name="context"></param>
        void OnPostConfigureServices(ServiceConfigurationContext context);

        /// <summary>
        /// 應用啓動前
        /// </summary>
        /// <param name="context"></param>
        void OnPreApplicationInitialization(ApplicationInitializationContext context);

        /// <summary>
        /// 應用啓動
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationInitialization(ApplicationInitializationContext context);

        /// <summary>
        /// 應用啓動後
        /// </summary>
        /// <param name="context"></param>
        void OnPostApplicationInitialization(ApplicationInitializationContext context);

        /// <summary>
        /// 應用中止
        /// </summary>
        /// <param name="context"></param>
        void OnApplicationShutdown(ApplicationShutdownContext context);
    }

修改 ModuleManager的 ConfigurationService、ApplicationInitialization、ApplicationShutdown 方法給調用傳遞對應參數
這部分代碼我就不貼了,會的大佬都能本身寫,想看的去個人github直接下載源碼看吧,麻煩老闆們給點個星星!!!

項目地址

知識全彙集,逐個擊破: https://github.com/MrChuJiu/Easy.Core.Flow

鳴謝

玩雙截棍的熊貓

源地址:https://github.com/rivenfx/Modular

相關文章
相關標籤/搜索