源码

GitHub:https://github.com/iamoldli/NetModular

演示地址

地址:https://nm.iamoldli.com

账户:admin

密码:admin

前端框架演示地址(临时)

地址:http://nm.demo.iamoldli.com/index.html

账户:admin

密码:admin

目录

1、开篇

2、快速创建一个业务模块

3、数据访问模块介绍

4、模块化实现思路

获取官方源码

为了方便查看源码,我们先获取下官方的源码

下载 AspNetCore 源码

git clone --recursive https://github.com/aspnet/AspNetCore

下载 Extensions 源码

git clone https://github.com/aspnet/Extensions.git

ASP.NET Core控制器的加载机制

参考文档:ASP.NET Core 中的应用程序部件

ASP.NET Core中通过应用程序部件ApplicationPart来发现控制器、视图组件或标记帮助程序等 MVC 功能,应用程序部件是由ApplicationPartManager类来管理。当调用AddMvc或者AddMvcCore方法添加MVC相关功能时,ASP.NET Core内部会创建ApplicationPartManager的实例,然后以入口程序集为起点,查找其依赖项树中的所有非官方包的程序集,并添加到它的ApplicationParts属性中,最后将ApplicationPartManager的实例以单例模式注入到容器中。下面是相关的源码:

源码路径:AspNetCore\src\Mvc.Core\src\DependencyInjection\MvcCoreServiceCollectionExtensions.cs

public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
} var partManager = GetApplicationPartManager(services);
//单例模式注入ApplicationPartManager
services.TryAddSingleton(partManager); ConfigureDefaultFeatureProviders(partManager);
ConfigureDefaultServices(services);
AddMvcCoreServices(services); var builder = new MvcCoreBuilder(services, partManager); return builder;
} //获取ApplicationPartManager实例
private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services)
{
var manager = GetServiceFromCollection<ApplicationPartManager>(services);
if (manager == null)
{
manager = new ApplicationPartManager(); var environment = GetServiceFromCollection<IHostingEnvironment>(services);
var entryAssemblyName = environment?.ApplicationName;
if (string.IsNullOrEmpty(entryAssemblyName))
{
return manager;
} manager.PopulateDefaultParts(entryAssemblyName);
} return manager;
}

源码路径:AspNetCore\src\Mvc.Core\src\ApplicationParts\ApplicationPartManager.cs

internal void PopulateDefaultParts(string entryAssemblyName)
{
var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName));
var assembliesProvider = new ApplicationAssembliesProvider(); //加载入口程序集的依赖项树中的所有非官方包的依赖程序集
var applicationAssemblies = assembliesProvider.ResolveAssemblies(entryAssembly); foreach (var assembly in applicationAssemblies)
{
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (var part in partFactory.GetApplicationParts(assembly))
{
ApplicationParts.Add(part);
}
}
}

因为我们的所有模块都是通过nuget包安装的,所以在编译时会自动引入到依赖项树中,也就是说,我们不需要手动加载模块中的程序集。

对于在编译时未引用的程序集,我们可以通过应用程序部件来手动加载

// create an assembly part from a class's assembly
var assembly = typeof(Startup).GetTypeInfo().Assembly;
services.AddMvc()
.AddApplicationPart(assembly); // OR
var assembly = typeof(Startup).GetTypeInfo().Assembly;
var part = new AssemblyPart(assembly);
services.AddMvc()
.ConfigureApplicationPartManager(apm => apm.ApplicationParts.Add(part));

模块的加载机制

NetModular的规则是在项目启动时,查找程序根目录下的modules目录,该目录专门用于保存所有模块的信息,它的结构如下:

modules目录下的每个子目录表示一个模块,每个子目录里面都有一个module.json文件,该文件用于描述模块信息,其结构如下:

{"Id": "Admin","Name":"权限管理","Version":"1.0.0"}
  • Note:module.json文件是在模块编译的时候自动生成并打包进Nuget包,当安装模块时会自动包含在项目中。这里用到了MSBuild,有兴趣的可以看看。 *

以下是生成module.json文件对应的配置信息

<Project>

  <PropertyGroup>
<ModulesDir>modules\$(Id)</ModulesDir>
<ModuleInfo>{"Id": "$(Id)","Name":"$(Name)","Version":"$(Version)"}</ModuleInfo>
</PropertyGroup> <ItemGroup>
<Content Include="$(ModulesDir)\**">
<Pack>true</Pack>
<PackagePath>contentFiles\any\any\$(ModulesDir)</PackagePath>
<PackageCopyToOutput>true</PackageCopyToOutput>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>Modules\$(Id)\%(RecursiveDir)%(FileName)%(Extension)</TargetPath>
</Content>
</ItemGroup> <Target Name="ModulesBuildBefore" AfterTargets="Build"> <!--创建modules目录-->
<MakeDir Directories="$(ModulesDir)"/> <!--生成module.json文件,Note:项目需要生成两次,否则Nuget包中的文件不是最新的-->
<WriteLinesToFile File="$(ModulesDir)\module.json" Overwrite="true" Lines="$(ModuleInfo)" /> </Target> </Project>

NetModular定义了一个描述模块信息的ModuleInfo.cs类和一个保存模块信息的IModuleCollection.cs接口

/// <summary>
/// 模块信息
/// </summary>
public class ModuleInfo
{
/// <summary>
/// 编号
/// </summary>
public string Id { get; set; } /// <summary>
/// 名称
/// </summary>
public string Name { get; set; } /// <summary>
/// 版本
/// </summary>
public string Version { get; set; } /// <summary>
/// 模块初始化器
/// </summary>
public IModuleInitializer Initializer { get; set; } /// <summary>
/// 程序集信息
/// </summary>
public ModuleAssembliesInfo AssembliesInfo { get; set; }
} /// <summary>
/// 模块集合
/// </summary>
public interface IModuleCollection : IList<ModuleInfo>
{ }

IModuleCollection有一个实现类ModuleCollection.cs,在该类的构造函数中执行加载模块列表的操作:

public ModuleCollection()
{
var moduleJsonFiles = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "modules"), "module.json", SearchOption.AllDirectories); foreach (var file in moduleJsonFiles)
{
var moduleInfo = JsonConvert.DeserializeObject<ModuleInfo>(File.ReadAllText(file));
if (moduleInfo != null)
{
//判断是否已存在
if (_moduleInfos.Any(m => m.Name.Equals(moduleInfo.Name)))
continue;
var assemblyHelper = new AssemblyHelper();
//此处默认模块命名空间前缀与当前项目相同
moduleInfo.AssembliesInfo = new ModuleAssembliesInfo
{
Domain = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Domain")).FirstOrDefault(),
Infrastructure = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Infrastructure")).FirstOrDefault(),
Application = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Application")).FirstOrDefault(),
Web = assemblyHelper.Load(m => m.Name.EndsWith("Module." + moduleInfo.Id + ".Web")).FirstOrDefault(),
}; Check.NotNull(moduleInfo.AssembliesInfo.Domain, moduleInfo.Id + "模块的Domain程序集未发现");
Check.NotNull(moduleInfo.AssembliesInfo.Infrastructure, moduleInfo.Id + "模块的Infrastructure程序集未发现");
Check.NotNull(moduleInfo.AssembliesInfo.Application, moduleInfo.Id + "模块的Application程序集未发现");
Check.NotNull(moduleInfo.AssembliesInfo.Web, moduleInfo.Id + "模块的Web程序集未发现"); //加载模块初始化器
var moduleInitializerType = moduleInfo.AssembliesInfo.Web.GetTypes().FirstOrDefault(t => typeof(IModuleInitializer).IsAssignableFrom(t));
if (moduleInitializerType != null && (moduleInitializerType != typeof(IModuleInitializer)))
{
moduleInfo.Initializer = (IModuleInitializer)Activator.CreateInstance(moduleInitializerType);
} Add(moduleInfo);
}
}
}

当项目启动时,首先创建ModuleCollection的实例,在它的构造函数中会加载所有模块信息,然后使用单例模式注入,这样就可以在系统中随时取用模块信息了。

/// <summary>
/// 添加模块
/// </summary>
/// <param name="services"></param>
/// <param name="env"></param>
/// <returns></returns>
public static IModuleCollection AddModules(this IServiceCollection services, IHostingEnvironment env)
{
//创建模块集合对象
var modules = new ModuleCollection();
services.AddSingleton<IModuleCollection>(modules); var cfgHelper = new ConfigurationHelper();
var cfg = cfgHelper.Load("module", env.EnvironmentName, true); //通用配置
services.Configure<ModuleCommonOptions>(cfg); foreach (var module in modules)
{
if (module == null)
continue; services.AddApplicationServices(module); if (module.Initializer != null)
{
module.Initializer.ConfigureServices(services); module.Initializer.ConfigOptions(services, cfg.GetSection(module.Id)); services.AddSingleton(module);
}
} return modules;
}

模块中的依赖注入和中间件处理

先看一下一个模块中包含哪些信息:

模块中的注入分为两类:

1、约定的

每个模块中都有配置项(Options)、实体(Entity)、仓储(Repository)、数据库上下文(DbContext)、工作单元(UnitOfWork)、服务(Service),他们都是约定好的,包括命名、目录、用法等,所以使用者只需要按照规则去使用即可,不需要关心注入的事情,它们在系统中是自动注入的。

以数据访问为例,数据访问相关的仓储(Repository)、数据库上下文(DbContext)、工作单元(UnitOfWork)是根据配置信息和模块来自动进行注入的,同时都是以Scoped方式注入。具体代码查看Data.AspNetCore项目。

2、自定义的

每个模块都可能会有一些独有的需要注入的服务,那么这些服务是属于自定义的,需要开发者自己手动注入。比如权限管理(Admin)模块中的权限验证处理(PermissionValidateHandler.cs),该类实现IPermissionValidateHandler接口,专门用于做权限验证功能。

除了注入以外,每个模块还有独有的中间件以及对某些功能的特殊配置,为了把这些信息一起集成到项目中,NetModular抽象了一个IModuleInitializer接口,该接口包括以下四个方法:

/// <summary>
/// 模块初始化器接口
/// </summary>
public interface IModuleInitializer
{
/// <summary>
/// 注入服务
/// </summary>
/// <param name="services"></param>
void ConfigureServices(IServiceCollection services); /// <summary>
/// 配置中间件
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
void Configure(IApplicationBuilder app, IHostingEnvironment env); /// <summary>
/// 配置MVC
/// </summary>
/// <param name="mvcOptions"></param>
void ConfigureMvc(MvcOptions mvcOptions); /// <summary>
/// 配置选项
/// </summary>
/// <param name="services"></param>
/// <param name="configuration"></param>
void ConfigOptions(IServiceCollection services, IConfiguration configuration);
}

方法说明:

1、ConfigureServices:用于注入服务

2、Configure:用于配置中间件

3、ConfigureMvc:用于配置MVC相关功能

4、ConfigOptions:用于配置模块的配置项

在每个模块中,都必须包含一个IModuleInitializer的实现ModuleInitializer,已权限管理(Admin)模块为例:

public class ModuleInitializer : IModuleInitializer
{
public void ConfigureServices(IServiceCollection services)
{
//权限验证服务
services.AddScoped<IPermissionValidateHandler, PermissionValidateHandler>();
} public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
} public void ConfigureMvc(MvcOptions mvcOptions)
{
// 审计日志过滤器
mvcOptions.Filters.Add(typeof(AuditingFilter));
} public void ConfigOptions(IServiceCollection services, IConfiguration configuration)
{
// Admin配置项
services.Configure<AdminOptions>(configuration);
}
}

当系统在启动的时候,会在指定的步骤,调用所有模块的对应方法,比如当调用service.AddModules方法时,会遍历模块并注入自定义服务和配置项,

public static IModuleCollection AddModules(this IServiceCollection services, IHostingEnvironment env)
{
var modules = new ModuleCollection();
services.AddSingleton<IModuleCollection>(modules); var cfgHelper = new ConfigurationHelper();
var cfg = cfgHelper.Load("module", env.EnvironmentName, true); services.Configure<ModuleCommonOptions>(cfg); // 遍历模块
foreach (var module in modules)
{
if (module == null)
continue; services.AddApplicationServices(module); // 判断IModuleInitializer实现是否存在
if (module.Initializer != null)
{
// 注入服务
module.Initializer.ConfigureServices(services); //配置配置项
module.Initializer.ConfigOptions(services, cfg.GetSection(module.Id)); services.AddSingleton(module);
}
} return modules;
}

至此,模块的所有信息都已集成到了系统当中~

原文首发:ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路

ASP.NET Core模块化前后端分离快速开发框架介绍之4、模块化实现思路的更多相关文章

  1. ASP.NET Core模块化前后端分离快速开发框架介绍之2、快速创建一个业务模块

    源码地址 GitHub:https://github.com/iamoldli/NetModular 演示地址 地址:https://nm.iamoldli.com 账户:admin 密码:admin ...

  2. ASP.NET Core模块化前后端分离快速开发框架介绍之1、开篇

    源码地址 GitHub:https://github.com/iamoldli/NetModular 演示地址 地址:https://nm.iamoldli.com 账户:admin 密码:admin ...

  3. ASP.NET Core模块化前后端分离快速开发框架介绍之3、数据访问模块介绍

    源码 GitHub:https://github.com/iamoldli/NetModular 演示地址 地址:https://nm.iamoldli.com 账户:admin 密码:admin 前 ...

  4. .Net Core+Vue.js模块化前后端分离快速开发框架NetModular更新日志(2019-12-08)

    源码 GitHub:https://github.com/iamoldli/NetModular 码云:https://gitee.com/laoli/NetModular 欢迎star~ 文档 ht ...

  5. .NET Core前后端分离快速开发框架(Core.3.0+AntdVue)

    .NET Core前后端分离快速开发框架(Core.3.0+AntdVue) 目录 引言 简介 环境搭建 开发环境要求 基础数据库构建 数据库设计规范 运行 使用教程 全局配置 快速开发 管理员登录 ...

  6. [转].NET Core前后端分离快速开发框架(Core.3.0+AntdVue)

    [转].NET Core前后端分离快速开发框架(Core.3.0+AntdVue) 目录 引言 简介 环境搭建 开发环境要求 基础数据库构建 数据库设计规范 运行 使用教程 全局配置 快速开发 管理员 ...

  7. .Net Core与Vue.js模块化前后端分离快速开发解决方案(NetModular)

    NetModular是什么? NetModular不仅仅是一个框架,它也是一整套的模块化与前后端分离的快速开发的解决方案,目标是致力于开箱即用,让开发人员完全专注于业务开发,不需要关心底层封装和实现. ...

  8. 优秀开源平台,前后端分离快速开发平台,一站式多端开发(PC+APP)

    JNPF平台架构介绍 JNPF快速开发平台采用前后端分离技术.采用B/S架构开发,形成一站式开发多端(APP+PC)使用. PC端版本介绍 第一个当然是当下热门的.net core了,运行环境为Vis ...

  9. ASP.NET WebApi+Vue前后端分离之允许启用跨域请求

    前言: 这段时间接手了一个新需求,将一个ASP.NET MVC项目改成前后端分离项目.前端使用Vue,后端则是使用ASP.NET WebApi.在搭建完成前后端框架后,进行接口测试时发现了一个前后端分 ...

随机推荐

  1. MATLAB模糊逻辑工具箱函数

    说明:本文档中所列出的函数适用于Matlab5.3以上版本,为了简明起见,只列出了函数名,若需要进一步的说明,请参阅MATLAB的帮助文档. 1. GUI工具 Anfisedit      打开ANF ...

  2. WPF Invoke与BeginInvoke的区别

    Control.Invoke 方法 (Delegate) :在拥有此控件的基础窗口句柄的线程上执行指定的委托. Control.BeginInvoke 方法 (Delegate) :在创建控件的基础句 ...

  3. 【总结整理】JavaScript的DOM事件学习(慕课网)

    事件:在文档或者浏览器窗口中发生的一些,特定的交互瞬间 HTML和JavaScript的交互通过事件 来实现 比如:1.滚动条向下滑动,加载图片 2.图片轮播,鼠标由2-5页调换 本章内容1.理解事件 ...

  4. Ubuntu 解决:当执行`sudo apt-get update`命令时 出现的 “apt-get 404 Not Found Package Repository Errors” 问题

    Ubuntu 解决:当执行sudo apt-get update或者sudo apt-get install命令是出现的 "apt-get 404 Not Found Package Rep ...

  5. vue.js基础学习(2)

    vm=new vue({ date:{name:"aa", user:{“name”:"lsm"}}); 获取属性值 1:vm.name 2:vm.$data. ...

  6. 如何解决Failed to start component [StandardEngine[Catalina].StandardHost[127.0.0.1].StandardContext[]]问题

    调试web项目,项目部署到tomcat,报如下错误: java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleExc ...

  7. bootstrap常用部件下载

    http://shapebootstrap.net/item/1524915-adminlte-dashboard-and-control-panel/live-demo

  8. c#事件1

    Private void button_clicked( object sender ,RouteEventArgs e) sender :引发事件的对象 源 e :      路由事件,提供可能重要 ...

  9. python drift

    install dependency(optional):(本文来自 不才b_d 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/sinat_36184075/arti ...

  10. Note: File Recipe Compression in Data Deduplication Systems

    Zero-Chunk Suppression 检测全0数据块,将其用预先计算的自身的指纹信息代替. Detect zero chunks and replace them with a special ...