专栏首页dotNET知音分享一个基于Net Core 3.1开发的模块化的项目

分享一个基于Net Core 3.1开发的模块化的项目

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

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

2.中间件涉及Redis、RabbitMQ等

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

github开源地址:

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 技术栈,所以愿意在开发社区分享我的知识成果,也想向社区的人学习更好的编码风格,更高一层编程技术.

本文分享自微信公众号 - dotNET知音(AAshiyou),作者:喻平勇

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • net core WebApi——使用xUnits来实现单元测试

    从开始敲代码到现在,不停地都是在喊着记得做测试,记得自测,测试人员打回来扣你money之类的,刚开始因为心疼钱(当然还是为了代码质量),就老老实实自己写完自己跑...

    李明成
  • .NET Core 3.0 单元测试与 Asp.Net Core 3.0 集成测试

    相信大家在看到单元测试与集成测试这个标题时,会有很多感慨,我们无数次的在实践中提到要做单元测试、集成测试,但是大多数项目都没有做或者仅建了项目文件。这里有客观原...

    李明成
  • abp模块生命周期设计思路剖析

    这个接口的参数是IAbpModule接口的类型,也就是派生自AbpModule的模块类型。该接口有一个默认实现,是一个抽象类,主要是用于复用。

    李明成
  • 小议Python的模块和包

    模块和包是比类更高一级的代码封装和复用,通过把相似的代码组织在一起使用,可以大量的减少程序的耦合。对于每个模块都有所谓的内部和外部之分,从这种角度来看,模块很像...

    哒呵呵
  • MyBatis批量插入获取自增ID

    MyBatis3.3.1或者MyBatis3.4.X(自测使用3.4.6) ModuleMapper.xml

    十毛
  • Webpack揭秘——走向高阶前端的必经之路

    随着前端工程化的不断发展,构建工具也在不断完善。作为大前端时代的新宠,webpack渐渐成为新时代前端工程师不可或缺的构建工具,随着webpack4的不断迭代,...

    疯狂的技术宅
  • Webpack揭秘——走向高阶前端的必经之路

    随着前端工程化的不断发展,构建工具也在不断完善。作为大前端时代的新宠,webpack渐渐成为新时代前端工程师不可或缺的构建工具,随着webpack4的不断迭代,...

    IMWeb前端团队
  • 第一批用基因编辑工具定制DNA的婴儿诞生

    11月26日,据人民网消息,来自中国深圳的科学家贺建奎在第二届国际人类基因组编辑峰会召开前一天宣布,一对名为露露和娜娜的基因编辑婴儿于11月在中国健康诞生。

    镁客网
  • 码农福利(一)

    整理了一些经典好书的电子书单,关注微信公众号JavaQ,并回复“JavaQ”,电子版轻松下载!码农福利将持续更新! ---- ? ? ? ? ? ? ? ? ?...

    JavaQ
  • SQL优化之踩过的坑

    正看资料看的过瘾,突然收到报警,说服务器负载太高,好吧,登录服务器看看,我擦嘞,还能不能愉快的玩耍了?下面是当时的负载情况 ? 看见mysql使用cpu已经到了...

    小小科

扫码关注云+社区

领取腾讯云代金券