分享一个基于Net Core 3.1开发的模块化的项目
先简单介绍下项目(由于重新基于模块化设计了整个项目,所以目前整个项目功能不多)
1.Asp.Net Core 3.1.2+MSSQL2019(LINUX版)
2.中间件涉及Redis、RabbitMQ等
3.完全模块化的设计,支持每个模块有独立的静态资源文件
github开源地址:
上一张项目结构图:
上图中 Modules目录下放的项目的模块
Mango.WebHost 承载整个项目运行
Mango.Framework 封装整个项目模块化核心
下面我会分享实现模块化的几个核心要点,更详细的我会在后续的博文中陆续发布.
框架如何去加载所写的模块这是最核心的问题之一,好在Asp.Net Core MVC为模块化提供了一个部件管理类
Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager
它支持从外部DLL程序集加载组件以及组件的管理.不过要从外部组件去获取哪些是组件我们需要借助一个工厂类ApplicationPartFactory,这个类支持从外部程序集得到对应的控制器信息,核心代码如下:
/// <summary> /// 向MVC模块添加外部应用模块组件 /// </summary> /// <param name="mvcBuilder"></param> /// <param name="assembly"></param> private static void AddApplicationPart(IMvcBuilder mvcBuilder, Assembly assembly) { var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); foreach (var part in partFactory.GetApplicationParts(assembly)) { mvcBuilder.PartManager.ApplicationParts.Add(part); } var relatedAssemblies = RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false); foreach (var relatedAssembly in relatedAssemblies) { partFactory = ApplicationPartFactory.GetApplicationPartFactory(relatedAssembly); foreach (var part in partFactory.GetApplicationParts(relatedAssembly)) { mvcBuilder.PartManager.ApplicationParts.Add(part); mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(relatedAssembly)); } } }
上面的代码展示了如何加载控制器信息,但是视图文件在项目生成的时候是单独的*.Views.dll文件,我们接下来介绍如何加载视图文件,同样还是用到了ApplicationPartManager类
mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(module.ViewsAssembly));
new一个CompiledRazorAssemblyPart对象表示添加进去的是视图编译文件,完整的核心代码
/// <summary> /// 添加MVC组件 /// </summary> /// <param name="services"></param> /// <returns></returns> public static IServiceCollection AddCustomizedMvc(this IServiceCollection services) { services.AddSession(); var mvcBuilder = services.AddControllersWithViews(options=> { //添加访问授权过滤器 options.Filters.Add(new AuthorizationComponentFilter()); }) .AddJsonOptions(options=> { options.JsonSerializerOptions.Converters.Add(new DateTimeToStringConverter()); }); foreach (var module in GlobalConfiguration.Modules) { if (module.IsApplicationPart) { if (module.Assembly != null) AddApplicationPart(mvcBuilder, module.Assembly); if (module.ViewsAssembly != null) mvcBuilder.PartManager.ApplicationParts.Add(new CompiledRazorAssemblyPart(module.ViewsAssembly)); } } return services; }
那如何去加载程序集呢?这里我使用了自定义的ModuleAssemblyLoadContext去加载程序集,这个类继承自AssemblyLoadContext(它支持卸载加载过的程序集,但是部件添加到MVC中时,好像不支持动态卸载会出现异常,可能是我还没研究透吧)
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; namespace Mango.Framework.Module { public class ModuleAssemblyLoadContext : AssemblyLoadContext { public ModuleAssemblyLoadContext() : base(true) { } } }
在使用ModuleAssemblyLoadContext类加载程序集时,先使用FileStream把程序集文件读取出来(这样能够避免文件一直被占用,方便开发中编译模块时报文件被占用的异常),加载文件路径时需要注意的问题一定要使用/(\在windows server下没问题,但是如果在linux下部署就会出现问题),代码如下:
/// <summary> /// 添加模块 /// </summary> /// <param name="services"></param> /// <param name="contentRootPath"></param> /// <returns></returns> public static IServiceCollection AddModules(this IServiceCollection services, string contentRootPath) { try { GlobalConfiguration.Modules = _moduleConfigurationManager.GetModules(); ModuleAssemblyLoadContext context = new ModuleAssemblyLoadContext(); foreach (var module in GlobalConfiguration.Modules) { var dllFilePath = Path.Combine(contentRootPath, $@"Modules/{module.Id}/{module.Id}.dll"); var moduleFolder = new DirectoryInfo(dllFilePath); if (File.Exists(moduleFolder.FullName)) { using FileStream fs = new FileStream(moduleFolder.FullName, FileMode.Open); module.Assembly = context.LoadFromStream(fs); // RegisterModuleInitializerServices(module, ref services); } else { _logger.Warn($"{dllFilePath} file is not find!"); } //处理视图文件程序集加载 var viewsFilePath = Path.Combine(contentRootPath, $@"Modules/{module.Id}/{module.Id}.Views.dll"); moduleFolder = new DirectoryInfo(viewsFilePath); if (File.Exists(moduleFolder.FullName)) { using FileStream viewsFileStream = new FileStream(moduleFolder.FullName, FileMode.Open); module.ViewsAssembly = context.LoadFromStream(viewsFileStream); } else { _logger.Warn($"{viewsFilePath} file is not find!"); } } } catch (Exception ex) { _logger.Error(ex); } return services; }
上面简单介绍了如何利用MVC自带的部件管理类去加载外部程序集,这里需要说明的一点的是每个模块我们采用创建区域的方式去区分模块,如下图展示的账号模块结构
基于模块化开发我们可能碰到一个比较常见的需求就是,如果每个模块需要拥有自己独立的静态资源文件呢?这种情况如何去解决呢?
好在MVC框架也提供了一个静态资源配置方法UseStaticFiles,我们在Configure方法中启用静态资源组件时,可以自定义设置静态文件访问的路径,设置代码如下
//设置每个模块约定的静态文件目录 foreach (var module in GlobalConfiguration.Modules) { if (module.IsApplicationPart) { var modulePath = $"{env.ContentRootPath}/Modules/{module.Id}/wwwroot"; if (Directory.Exists(modulePath)) { app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFileProvider(modulePath), RequestPath = new PathString($"/{module.Name}") }); } } }
上述代码片段中我们能够看到通过new StaticFileOptions()添加配置项, StaticFileOptions中有两个重要的属性,只需要配置好这两个就能满足基本需求了
FileProvider:该属性表示文件的实际所在目录(如:{env.ContentRootPath}/Modules/Mango.Module.Account/wwwroot)
RequestPath:该属性表示文件的请求路径(如 /account/test.js 这样访问到就是 {env.ContentRootPath}/Modules/Mango.Module.Account/wwwroot下的test.js文件)
这篇博文我就暂时只做一个模块化开发实现的核心代码展示和说明,更具体的只能在接下来的博文中展示了.
其实我也开发了一个前后分离的,只剩下鉴权,实现的核心和上面所写的一样,这里我就只把开源地址分享出来,我后面还是会用业余时间来继续完成它
该项目我已经在linux 上使用docker容器部署了,具体地址我就不发布了(避免打广告的嫌疑,我截几张效果图)
结语:这个项目我会一个更新下去,接下去这个框架会向DDD发展.
因为喜欢.net 技术栈,所以愿意在开发社区分享我的知识成果,也想向社区的人学习更好的编码风格,更高一层编程技术.
- 用VSCode开发一个基于asp.net core 2.0/sql server linux(docker)/ng5/bs4的项目(1)
- 使用Jquery+EasyUI进行框架项目开发案例解说之中的一个---员工管理源代码分享
- Go/Python/Erlang编程语言对比分析及示例 基于RabbitMQ.Client组件实现RabbitMQ可复用的 ConnectionPool(连接池) 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil 分享基于MemoryCache(内存缓存)的缓存工具类,C# B/S 、C/S项目均可以使用!
- 5年以前开发一个消费场所会员管理软件的开发经验分享,小项目一般人折腾不起,靠小项目比较难创业成功
- 5年以前开发一个消费场所会员管理软件的开发经验分享,小项目一般人折腾不起,靠小项目比较难创业成功
- 基于maven的javaweb项目模块化开发
- 3年前的一个小项目经验,分享给菜鸟兄弟们(公文收发小软件:程序开发部分)
- 使用Jquery+EasyUI进行框架项目开发案例解说之中的一个---员工管理源代码分享
- 一个利用微信分享的项目开发过程以及后续思考
- SpringMVC经典系列-04基于Spring3.1注解的方式进行项目开发---【LinusZhu】
- 3年前的一个小项目经验,分享给菜鸟兄弟们(公文收发小软件:程序开发部分)
- 使用Jquery+EasyUI进行框架项目开发案例解说之中的一个---员工管理源代码分享
- 基于maven的javaweb项目模块化开发
- 5年以前开发一个消费场所会员管理软件的开发经验分享,小项目一般人折腾不起,靠小项目比较难创业成功
- 最新基于Vue.js 2.0的UI组件库快速开发一个Vue.jsWeb应用ElementUI项目实战
- 3年前的一个小项目经验,分享给菜鸟兄弟们(公文收发小软件:程序开发部分)
- 最近的一个项目中基于MsCrm二次开发的操作总结
- 分享一个简易的ORM框架源代码以及基于该框架开发的一个简易论坛源代码
- 开发者分享 | 从零开始开发一个即时通讯项目
- 基于vue-cli网上商城项目实战开发——搭建一个完整的SPA项目开发框架(一)