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

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,这个类支持从外部程序集得到对应的控制器信息,核心代码如下:

         /// <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文件)

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

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

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

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

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

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

分享一个基于Net Core 3.1开发的模块化的项目的更多相关文章

  1. 一个基于 .NET Core 2.0 开发的简单易用的快速开发框架 - LinFx

    LinFx 一个基于 .NET Core 2.0 开发的简单易用的快速开发框架,遵循领域驱动设计(DDD)规范约束,提供实现事件驱动.事件回溯.响应式等特性的基础设施.让开发者享受到正真意义的面向对象 ...

  2. 转: Orz是一个基于Ogre思想的游戏开发架构

    Orz是一个基于Ogre思想的游戏开发架构,好的结构可以带来更多的功能.Orz和其他的商业以及非商业游戏开发架构不同.Orz更专著于开发者的感受,简化开发者工作.Orz可以用于集成其他Ogre3D之外 ...

  3. 如何创建一个基于 .NET Core 3 的 WPF 项目

    在 Connect(); 2018 大会上,微软发布了 .NET Core 3 Preview,以及基于 .NET Core 3 的 WPF:同时还发布了 Visual Studio 2019 预览版 ...

  4. Swift:一个基于.NET Core的分布式批处理框架

    Swift是什么 从文章的标题可知:此Swift非Apple那个Swift,只是考虑这个词的含义比较适合. Swift是一个基于.NET Core的分布式批处理框架,支持将作业分割后分发到多台服务器并 ...

  5. [UWP]分享一个基于HSV色轮的调色板应用

    1. 前言 上一篇文章介绍了HSV色轮,这次分享一个基于HSV色轮的调色板应用,应用地址:ColorfulBox - Microsoft Store 2. 功能 ColorfulBox是Adobe 色 ...

  6. 分享一个基于 ABP(.NET 5.0) + vue-element-admin 管理后台

    1.前言 分享一个基于ABP(.NET 5.0) + vue-element-admin项目.希望可以降低新手对于ABP框架的学习成本,感兴趣的同学可以下载项目启动运行一下.对于想选型采用ABP框架的 ...

  7. 分享一个基于Abp Vnext开发的API网关项目

    这个项目起源于去年公司相要尝试用微服务构建项目,在网关的技术选型中,我们原本确认了ApiSix 网关,如果需要写网关插件需要基于Lua脚本去写,我和另外一个同事当时基于这个写了一个简单的插件,但是开发 ...

  8. 初识TPOT:一个基于Python的自动化机器学习开发工具

    1. TPOT介绍 一般来讲,创建一个机器学习模型需要经历以下几步: 数据预处理 特征工程 模型选择 超参数调整 模型保存 本文介绍一个基于遗传算法的快速模型选择及调参的方法,TPOT:一种基于Pyt ...

  9. flipt 一个基于golang 的特性工具开发类库

    以前介绍过一个Flagr 的基于golang 的特性功能开发类库(技术雷达推荐),今天看到一个类似也很不错的方案flipt 参考架构 包含的特性 快速,使用golang 编写,同时进行了性能优化 运行 ...

随机推荐

  1. Jenkins+jmeter设置定时执行任务

    1.准备好你的jmeter脚本 2.测试命令行下脚本执行 1)cd进入脚本目录 2)dir命令查看该目录下的脚本文件 3)jmeter -n -t test.jmx -l result.jtl  命令 ...

  2. “冰桶挑战”之外:微软科技助力ALS患者

    编者按:"直到ALS出现治疗方法,科技就是我的解药."ALS患者,前美国橄榄球联盟(NFL)球员Steve Gleason如是说.最近,一支微软首届黑客马拉松(Hackathon) ...

  3. 合并cookie,提取json数据

    发送的第3个请求需要前两个请求的cookie,需要对cookie进行合并 发送的请求数据来自于json数据中的某个键值. 这里是删除所有的对话主题目录,每一个目录有一个id,发起删除对话主题目录的请求 ...

  4. Java实用教程系列之对象的转型

    体现: 父类的引用可以指向子类的对象接口的引用可以指向实现类的对象转型: 向上转型由子类类型转型为父类类型,或者由实现类类型转型为接口类型向上转型一定会成功,是一个隐式转换向上转型后的对象,将只能访问 ...

  5. [洛谷P4549] [模板] 裴蜀定理

    18.10.03模拟赛T1. 出题人xcj(Mr.Handsome)十分良心,给了一道送分题...... 互测题好久没有出现送分题了.xcj真棒. 题目传送门 幸亏之前看过,否则真的是送分题都拿不到. ...

  6. Web窗体--控件

    服务器基本控件:button: text属性linkbutton:text属性,它是一个超链接模样的普通buttonhyperlink: navigateurl:链接地址,相当于<a>标签 ...

  7. 【转】蛋糕尺寸(寸)、尺寸(CM)、重量(磅)、食用人数对照换算参考表

    转自:https://www.douban.com/note/324832054/ 蛋糕尺寸(寸).尺寸(CM).重量(磅).食用人数对照换算参考表 馋嘴猫DIY烘焙 2014-01-04 12:15 ...

  8. 理解 Redux 中间件机制

    Redux 的 action 是一个 JS 对象,它表明了如何对 store 进行修改.但是 Redux 的中间件机制使action creator 不光可以返回 action 对象,也可以返回 ac ...

  9. Ionic3学习笔记(七)Storage

    本文为原创文章,转载请标明出处 目录 简介 安装 配置 使用 1. 简介 Storage可以很容易的存储键值对和JSON对象.Storage在底层使用多种存储引擎,根据运行平台选择最佳的存储方式. 当 ...

  10. IDEA工具java.io.IOException: Could not find resource SqlMapConfig.xml

    IDEA工具java.io.IOException: Could not find resource SqlMapConfig.xml 解决办法: 1.删掉pom.xml文件的这行代码 <pac ...