分享一个基于Net Core 3.1开发的模块化的项目(补充数据库脚本)

先简单介绍下项目(由于重新基于模块化设计了整个项目,所以目前整个项目功能不多)

1.Asp.Net Core 3.1.2+MSSQL2019(LINUX版)

2.中间件涉及Redis、RabbitMQ等

3.完全模块化的设计,支持每个模块有独立的静态资源文件

github开源地址(数据库脚本也已经上传到github打开下面地址就能看到SQL文件):

https://github.com/yupingyong/mango

上一张项目结构图:

上图中 Modules目录下放的项目的模块

Mango.WebHost 承载整个项目运行

Mango.Framework 封装整个项目模块化核心

下面我会分享实现模块化的几个核心要点,更详细的我会在后续的博文中陆续发布.

框架如何去加载所写的模块这是最核心的问题之一,好在Asp.Net Core MVC为模块化提供了一个部件管理类

Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager

它支持从外部DLL程序集加载组件以及组件的管理.不过要从外部组件去获取哪些是组件我们需要借助一个工厂类ApplicationPartFactory,这个类支持从外部程序集得到对应的控制器信息,核心代码如下:

1         /// <summary>
 2         /// 向MVC模块添加外部应用模块组件
 3         /// </summary>
 4         /// <param name="mvcBuilder"></param>
 5         /// <param name="assembly"></param>
 6         private static void AddApplicationPart(IMvcBuilder mvcBuilder, Assembly assembly)
 7         {
 8             var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
 9             foreach (var part in partFactory.GetApplicationParts(assembly))
10             {
11                 mvcBuilder.PartManager.ApplicationParts.Add(part);
12             }
13
14             var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false);
15             foreach (var relatedAssembly in relatedAssemblies)
16             {
17                 partFactory = ApplicationPartFactory.GetApplicationPartFactory(relatedAssembly);
18                 foreach (var part in partFactory.GetApplicationParts(relatedAssembly))
19                 {
20                     mvcBuilder.PartManager.ApplicationParts.Add(part);
21                     mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(relatedAssembly));
22                 }
23             }
24         }

上面的代码展示了如何加载控制器信息,但是视图文件在项目生成的时候是单独的*.Views.dll文件,我们接下来介绍如何加载视图文件,同样还是用到了ApplicationPartManager类

mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(module.ViewsAssembly));

new一个CompiledRazorAssemblyPart对象表示添加进去的是视图编译文件,完整的核心代码

1         /// <summary>
 2         /// 添加MVC组件
 3         /// </summary>
 4         /// <param name="services"></param>
 5         /// <returns></returns>
 6         public static IServiceCollection AddCustomizedMvc(this IServiceCollection services)
 7         {
 8             services.AddSession();
 9
10             var mvcBuilder = services.AddControllersWithViews(options=> {
11                 //添加访问授权过滤器
12                 options.Filters.Add(new AuthorizationComponentFilter());
13             })
14                 .AddJsonOptions(options=> {
15                     options.JsonSerializerOptions.Converters.Add(new DateTimeToStringConverter());
16                 });
17             foreach (var module in GlobalConfiguration.Modules)
18             {
19                 if (module.IsApplicationPart)
20                 {
21                     if (module.Assembly != null)
22                         AddApplicationPart(mvcBuilder, module.Assembly);
23                     if (module.ViewsAssembly != null)
24                         mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(module.ViewsAssembly));
25                 }
26             }
27             return services;
28         }

那如何去加载程序集呢?这里我使用了自定义的ModuleAssemblyLoadContext去加载程序集,这个类继承自AssemblyLoadContext(它支持卸载加载过的程序集,但是部件添加到MVC中时,好像不支持动态卸载会出现异常,可能是我还没研究透吧)

1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Threading.Tasks;
 5 using System.Reflection;
 6 using System.Runtime.CompilerServices;
 7 using System.Runtime.Loader;
 8
 9 namespace Mango.Framework.Module
10 {
11     public class ModuleAssemblyLoadContext : AssemblyLoadContext
12     {
13         public ModuleAssemblyLoadContext() : base(true)
14         {
15         }
16     }
17 }

在使用ModuleAssemblyLoadContext类加载程序集时,先使用FileStream把程序集文件读取出来(这样能够避免文件一直被占用,方便开发中编译模块时报文件被占用的异常),加载文件路径时需要注意的问题一定要使用/(\在windows server下没问题,但是如果在linux下部署就会出现问题),代码如下:

1         /// <summary>
 2         /// 添加模块
 3         /// </summary>
 4         /// <param name="services"></param>
 5         /// <param name="contentRootPath"></param>
 6         /// <returns></returns>
 7         public static IServiceCollection AddModules(this IServiceCollection services, string contentRootPath)
 8         {
 9             try
10             {
11                 GlobalConfiguration.Modules = _moduleConfigurationManager.GetModules();
12                 ModuleAssemblyLoadContext context = new ModuleAssemblyLoadContext();
13                 foreach (var module in GlobalConfiguration.Modules)
14                 {
15                     var dllFilePath = Path.Combine(contentRootPath, $@"Modules/{module.Id}/{module.Id}.dll");
16                     var moduleFolder = new DirectoryInfo(dllFilePath);
17                     if (File.Exists(moduleFolder.FullName))
18                     {
19                         using FileStream fs = new FileStream(moduleFolder.FullName, FileMode.Open);
20                         module.Assembly = context.LoadFromStream(fs);
21                         //
22                         RegisterModuleInitializerServices(module, ref services);
23                     }
24                     else
25                     {
26                         _logger.Warn($"{dllFilePath} file is not find!");
27                     }
28                     //处理视图文件程序集加载
29                     var viewsFilePath = Path.Combine(contentRootPath, $@"Modules/{module.Id}/{module.Id}.Views.dll");
30                     moduleFolder = new DirectoryInfo(viewsFilePath);
31                     if (File.Exists(moduleFolder.FullName))
32                     {
33                         using FileStream viewsFileStream = new FileStream(moduleFolder.FullName, FileMode.Open);
34                         module.ViewsAssembly = context.LoadFromStream(viewsFileStream);
35                     }
36                     else
37                     {
38                         _logger.Warn($"{viewsFilePath} file is not find!");
39                     }
40                 }
41             }
42             catch (Exception ex)
43             {
44                 _logger.Error(ex);
45             }
46             return services;
47         }

上面简单介绍了如何利用MVC自带的部件管理类去加载外部程序集,这里需要说明的一点的是每个模块我们采用创建区域的方式去区分模块,如下图展示的账号模块结构

基于模块化开发我们可能碰到一个比较常见的需求就是,如果每个模块需要拥有自己独立的静态资源文件呢?这种情况如何去解决呢?

好在MVC框架也提供了一个静态资源配置方法UseStaticFiles,我们在Configure方法中启用静态资源组件时,可以自定义设置静态文件访问的路径,设置代码如下

1             //设置每个模块约定的静态文件目录
 2             foreach (var module in GlobalConfiguration.Modules)
 3             {
 4                 if (module.IsApplicationPart)
 5                 {
 6                     var modulePath = $"{env.ContentRootPath}/Modules/{module.Id}/wwwroot";
 7                     if (Directory.Exists(modulePath))
 8                     {
 9                         app.UseStaticFiles(new StaticFileOptions()
10                         {
11                             FileProvider = new PhysicalFileProvider(modulePath),
12                             RequestPath = new PathString($"/{module.Name}")
13                         });
14                     }
15                 }
16             }

上述代码片段中我们能够看到通过new StaticFileOptions()添加配置项, StaticFileOptions中有两个重要的属性,只需要配置好这两个就能满足基本需求了

FileProvider:该属性表示文件的实际所在目录(如:{env.ContentRootPath}/Modules/Mango.Module.Account/wwwroot)

RequestPath:该属性表示文件的请求路径(如 /account/test.js 这样访问到就是 {env.ContentRootPath}/Modules/Mango.Module.Account/wwwroot下的test.js文件)

这篇博文我就暂时只做一个模块化开发实现的核心代码展示和说明,更具体的只能在接下来的博文中展示了.

其实我也开发了一个前后分离的,只剩下鉴权,实现的核心和上面所写的一样,这里我就只把开源地址分享出来,我后面还是会用业余时间来继续完成它

https://github.com/yupingyong/mango-open

该项目我已经在linux 上使用docker容器部署了,具体地址我就不发布了(避免打广告的嫌疑,我截几张效果图)

结语:这个项目我会一个更新下去,接下去这个框架会向DDD发展.

因为喜欢.net 技术栈,所以愿意在开发社区分享我的知识成果,也想向社区的人学习更好的编码风格,更高一层编程技术.

(0)

相关推荐

  • 关于前端开发中的模块化

    前端开发离不开模块化,与模块化有关的关键字有以下几个: require/module.exports export/import define/require/exprots define/seajs ...

  • 分享一个基于 SpringBoot uni-app 的网易音乐云村项目

    功能简介 微信登录,发文本动态,发图文动态,发视频动态,关注,转发,评论,点赞,收藏等等 截图 技术栈 后端:Java,SpringBoot,MyBatis,MySQL,Redis 前端:uni-ap ...

  • 好文分享 | 基于IPD模式的产品开发是怎样的?

    导读: 在企业咨询和培训过程中,经常被问到的一个问题是具体某一个开发项目如何在IPD中一步一步开展!这其实涉及到IPD体系和项目管理体系两大管理系统的融合,如项目管理十大知识领域如何在IPD流程中开展 ...

  • 分享一个最最基本实用的开发流程

    「开发流程」在不同的产品项目中,不同规模的企业中,往往也不尽相同,有瀑布.有敏捷,但最基本的开发流程理当如图所示,也是最简单最容易实操,公认度最高 如果实践这套流程,说明你们在甲方爸爸面前比较硬气的那 ...

  • 今天给大家分享一个治疗肝肾两虚的方子:熟...

    今天给大家分享一个治疗肝肾两虚的方子:熟地黄.肉苁蓉.菟丝子.巴戟天.枸杞子.车前子.白菊花. 主治:肝肾两虚.视物昏暗.眼前黑花.迎风流泪.

  • 今天给大家分享一个治疗呃逆(打嗝)的方子...

    今天给大家分享一个治疗呃逆(打嗝)的方子. 组方:制附子.干姜.白术.白芍.茯苓.人参.甘草. #打嗝##中医#

  • 分享一个胃脘痛的治疗方药 ...

    分享一个胃脘痛的治疗方药 由于胃溃疡引起的胃脘痛,用活血化瘀浓缩液治疗,效果甚好,介绍如下: [临床表现]胃脘刺痛,或饭前发作,或伴吞酸.吐酸.食少.消瘦,或有大便色褐.舌有瘀斑或正常,脉弦. [治法 ...

  • 【分享一个缓解滑膜炎的热敷小方子】滑膜炎...

    [分享一个缓解滑膜炎的热敷小方子] 滑膜炎在中医学中属"痹症",是经络不通.气血运行不畅.寒气流入关节所致 可以通过改善骨关节血液循环障碍,能有效缓解滑膜炎带来的不适症状 有关节炎 ...

  • 今天给大家分享一个治疗外感风热的方子,适...

    今天给大家分享一个治疗外感风热的方子,适合热汗不止.口鼻干燥.微渴思饮.咳嗽少痰等症状. 组方:金银花.连翘.荆芥穗.薄荷.淡豆豉.苦桔梗.牛蒡子.竹叶.甘草.鲜苇根. 作汤,煎数沸,日服四饮. #中 ...

  • 【水草造景】分享一个插种式水上叶水草盆景

    大家好,我是净植画社舒大先生,有幸在[rlyl的自然世界]平台给大家分享一下我制作的插种水上叶盆景.因为从草缸的水草修剪下来扔掉很可惜,于是我花了半年时间尝试培养水上叶,并把水上叶修剪出来进行插种.期 ...