知识全聚集 .Net Core 技术突破 | 如何实现一个模块化方案一
简介
模块化的介绍一共2篇
这一篇我们实现一个功能非常简单的StartupModules模块化。
第二篇我们来实现一个ABP的模块化效果。
思考
其实来简单想一下模块化的实验思路,写个接口=>模块类继承该接口=>项目启动反射检索=>调用接口实现。 那么具体到代码实践应该怎么写呢。
开始
第一步
第一步就是写一个模块化接口类的嘛! 新建类 IStartupModule
然后写一个反射检索全文谁继承了这个接口的方法 新建类 StartupModulesOptions
代码解释:Activator.CreateInstance 与指定参数匹配程度最高的构造函数来创建指定类型的实例 ps:白话文就是,你给我Type我给你创建个对应的实例 更一个有意思的是 Assembly.GetEntryAssembly()! 这个! 是不是很好奇怕 ps:我第一次看到这个语法也蒙了,问了好多人大家都没用过,这个语法同TS中的断言,是非null类型断言,意思就是我断言我这个方法返回的内容绝对不是null。
第二步
到这里来看我们是不是已经拿到了所有继承接口的模块那么怎么在该调用的地方调用呢,缺啥写啥
第三步
运行代码也有了,我该怎么调用呢。 接下来只要在 在Program 的 WebHost 调用.UseStartupModules() 流程就可以加载我们的 ConfigureServices 了
中间来插播一下
请让我掏出来一个器大的东西来说 他就是: IStartupFilter
,这个东西是个啥东西呢。来看一段源码
namespace Microsoft.AspNetCore.Hosting
{
public interface IStartupFilter
{
Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
}
}
这里我们看到了 Configure 他返回一个 IApplicationBuilder 他是怎么用的呢 我们新建一个空的Web项目的时候不知道有没有注意过 UseStaticFiles 这个函数
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
}
在这个方法中,你可以直接使用方法提供的IApplicationBuilder参数,并且可以向其中添加各种中间件。使用IStartupFilter, 你可以指定并返回一个Action类型的泛型委托,这意味你除了可以使用方法提供的泛型委托配置IApplicationBuilder对象, 还需要返回一个泛型委托。
我们来看一段代码 Build(); 这个会调用BuildApplication方法
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.Build();
host.Run();
}
}
private RequestDelegate BuildApplication()
{
..
IApplicationBuilder builder = builderFactory.CreateBuilder(Server.Features);
builder.ApplicationServices = _applicationServices;
var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
Action<IApplicationBuilder> configure = _startup.Configure;
foreach (var filter in startupFilters.Reverse())
{
configure = filter.Configure(configure);
}
configure(builder);
return builder.Build();
}
首先,此方法创建IApplicationBuilder的实例,该实例将用于构建中间件管道,并将ApplicationServices设置为已配置的DI容器。
接下来的代码块很意思。首先,从DI容器中获取了一个集合IEnumerable<IStartupFilter>
我们可以配置多个IStartupFilter来形成一个管道,所以这个方法只是从容器中取出它们。
现在我们通过循环遍历每个IStartupFilter(以相反的顺序),传入Startup.Configure方法,然后更新局部变量configure来创建Configure方法的管道。
第四步
我们自己如何来实现 一个IStartupFilter 让他帮我们调用 Configure。
第五步
在 WebHostBuilderExtensions类 UseStartupModules 方法 ConfigureServices 下用 IStartupFilter 注入实现 这样在Build() 的时候就会调用模块的方法了
ActivatorUtilities.CreateInstance<ModulesStartupFilter>(sp, runner) // 第二个参数是在创建实例的时候 给构造函数注入的第一个参数
测试一下
新建 Core Web项目 在 Program.cs
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
// 进行模块映射
webBuilder.UseStartupModules().UseStartup<Startup>();
});
在 Startup.cs ConfigureServices和Configure 下打一个 Console.WriteLine
新建 类 HangfireStartupModule 继承 IStartupModule
public class HangfireStartupModule : IStartupModule
{
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine("HangfireStartupModule----ConfigureServices");
}
public void Configure(IApplicationBuilder app)
{
Console.WriteLine("HangfireStartupModule----Configure");
}
}
结果
一个非常简单的模块化就完工了,当然这个是基础版本,只能拿来借鉴思路学习下。
补充模块的 ConfigureServices 和 Configure 传递上下文
新建类 ConfigureServicesContext 和 ConfigureMiddlewareContext
public class ConfigureMiddlewareContext
{
public ConfigureMiddlewareContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, IServiceProvider serviceProvider, StartupModulesOptions options)
{
Configuration = configuration;
HostingEnvironment = hostingEnvironment;
ServiceProvider = serviceProvider;
Options = options;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment HostingEnvironment { get; }
public IServiceProvider ServiceProvider { get; }
public StartupModulesOptions Options { get; }
}
public class ConfigureServicesContext
{
public ConfigureServicesContext(IConfiguration configuration, IWebHostEnvironment hostingEnvironment, StartupModulesOptions options)
{
Configuration = configuration;
HostingEnvironment = hostingEnvironment;
Options = options;
}
public IConfiguration Configuration { get; }
public IWebHostEnvironment HostingEnvironment { get; }
public StartupModulesOptions Options { get; }
}
修改 StartupModuleRunner 的方法
public void ConfigureServices(IServiceCollection services, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
{
var ctx = new ConfigureServicesContext(configuration, hostingEnvironment, _options);
foreach (var cfg in _options.StartupModules)
{
cfg.ConfigureServices(services, ctx);
}
}
public void Configure(IApplicationBuilder app, IConfiguration configuration, IWebHostEnvironment hostingEnvironment)
{
using (var scope = app.ApplicationServices.CreateScope()) {
var ctx = new ConfigureMiddlewareContext(configuration, hostingEnvironment, scope.ServiceProvider, _options);
foreach (var cfg in _options.StartupModules)
{
cfg.Configure(app, ctx);
}
}
}
鸣谢
玩双截棍的熊猫、NETCore-大黄瓜
思路来源:https://github.com/henkmollema/StartupModules
友联:https://github.com/DestinyCore/Destiny.Core.Flow
- nginx域名访问的白名单配置梳理
- 老生常谈:利用Membership实现SSO(单点登录)
- nginx利用geo模块做限速白名单以及geo实现全局负载均衡的操作记录
- Mysql高效插入/更新数据
- 宋小菜融资2.3亿元!域名保护意识强
- 世界最奇葩的7款机器人
- Mysql高效插入/更新数据
- 关于Membership/Role您可能不知道的细节
- Sqlite向MySql导入数据
- 未来3年,人工智能如何影响法律行业?5位权威专家给出趋势
- Java 常见内存溢出异常与代码实现
- nginx限制上传大小和超时时间设置说明/php限制上传大小
- Unity Application Block 1.2 学习笔记
- 苹果首个自动驾驶专利到底有什么来头?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android开发中方向传感器定义与用法详解【附指南针实现方法】
- Android利用ViewDragHelper轻松实现拼图游戏的示例
- ListView实现聊天列表之处理不同数据项
- 运算符
- ViewPager实现带引导小圆点与自动跳转的引导界面
- Android开发中Launcher3常见默认配置修改方法总结
- Android辅助功能实现自动抢红包(附源码)
- Android设置控件阴影的三种方法
- Android Studio 引入 aidl 文件的方法汇总
- Android 点击生成二维码功能实现代码
- Android屏幕手势检测的实现代码
- Android开发实现简单的观察者与被观察者示例
- Android开发中4个常用的工具类【Toast、SharedPreferences、网络及屏幕操作】
- Android实现客户端语音动弹界面实例代码
- Android封装MVP实现登录注册功能