- A+
引言
很多看了上一章的朋友私信博主,问如何自定义,自己的中间件(Middleware),毕竟在实际的项目中,大家会有很多需求要用到中间件,比如防盗链、缓存、日志等等功能,于是博主这边就简单讲解一下框架、组件惯用的优雅手法,官方也推荐这种写法,这样会使得我们扩展性更好,也不会破坏原本结构。
什么是中间件
中间件是一种装配到应用管道以处理请求和响应的软件。 每个组件:
- 选择是否将请求传递到管道中的下一个组件。
- 可在管道中的下一个组件前后执行工作。
使用 RunMap 和 Use 扩展方法来配置请求委托,请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。
简单的说,我们按需求决定使用哪些组件,程序运行时,一个HTTP请求过来,程序执行流程,是按照我们定义的组件顺序执行的。所以我们项目上的中间件放置顺序是不能乱的,并且不用的也不要装配,避免消耗性能。
概念图:
如果想做其他相关了解,博主建议直接在官网上看文档,微软的文档写的还是很好的,还提供的Demo下载,比很多网上博客讲的好。
微软官网地址:ASP.NET Core 中间件 | Microsoft Docs
接下来进入正题,我们写一个,把所有http请求地址发送到MQ的中间件:
1.编写MsgMiddleware类
这里我们就使用直接编写Middleware类的方式,大家也可以使用实现IMiddleware接口的方式。两种底层原理不一样:前者是框架启动时就实例化;后者是请求来时才实例化,用完立即释放。
编写Middleware类注意:
- 编写好InvokeAsync或者Invoke方法
- 构造函数参数需要一个RequestDelegate类型的委托
因为这块源码是直接通过反射创建和调用的,不过源码也会对我们的类进行规范校验。
代码逻辑:
这里我们就实现一个简单逻辑,根据Options配置SendFlag是否开启发送MQ,如果是,就调用我们的ISendMessage发送服务。服务获取方式大家也可以使用注入的方式
ISendMessage服务、Options配置类代码,我放到了后面讲解,毕竟逻辑简单,而且也不是重点。
查看代码
public class MsgMiddleware { private readonly RequestDelegate _next; private readonly MsgOptions options; /// <summary> /// 管道执行到该中间件时候下一个中间件的RequestDelegate请求委托,如果有其它参数,也同样通过注入的方式获得 /// </summary> /// <param name="next"></param> public MsgMiddleware(RequestDelegate next, IOptions<MsgOptions> options) { //通过注入方式获得对象 _next = next; this.options = options.Value; } /// <summary> /// 自定义中间件要执行的逻辑 /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task Invoke(HttpContext context) { if (options.SendFlag) { //通过IOC获取ISendMessage ISendMessage _message = context.RequestServices.GetService<ISendMessage>(); _message.Send(context.Request.Path.Value); } //把context传进去执行下一个中间件 await _next(context); } }
2.编写IApplicationBuilder扩展方法
这里我们就定义两个扩展方法,用来应对在Use时候直接配置Options,或者后面通过IOC方式配置Options
注意:如果在Use时候直接配置参数,我们的Options需要通过Options.Create(op)帮我们包裹成IOptions<>类型,因为编写的中间件参数是Options模式的一个IOptions接口
查看代码
public static class MsgBuilderMiddlewareExtensions { //没有Option,依靠IOC的Add的方式设置 public static IApplicationBuilder UseMsgSend(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMiddleware<MsgMiddleware>(); } //Use直接配置Options public static IApplicationBuilder UseMsgSend(this IApplicationBuilder app, Action<MsgOptions> optionsAction) { if (app == null) { throw new ArgumentNullException(nameof(app)); } //1 不能直接初始化Option //2 也不能找到ServiceCollection去初始化了 MsgOptions op = new MsgOptions(); optionsAction(op); return app.UseMiddleware<MsgMiddleware>(Options.Create(op)); } }
3.编写IServiceCollection容器扩展方法
这样的好处是,我们这样可以集中注册内部映射,隐藏实现细节,外部不需要关心,这个好处博主就不多说了,因为大家使用别人写的组件也心有体会,对于使用者来说只需要关心怎么用,他内部有多么复杂的逻辑是不知道的,也不需要知道.
这里也是编写两个扩展方法,用来应对在Use时候直接配置Options,或者后面通过IOC方式配置Options
代码逻辑:
这两个方法里面逻辑就是,注册好业务服务、Options 等等。。。
查看代码
/// <summary> /// 这样可以集中注册内部映射,外部不需要关心 /// </summary> public static class ServiceCollectionExtensions { /// <summary> /// 配置信息初始化由Middleware /// </summary> /// <param name="services"></param> /// <returns></returns> public static IServiceCollection AddSendMessage(this IServiceCollection services) { return services.AddSingleton<ISendMessage, SendMessage>(); } /// <summary> /// 配置信息直接用Option的模式去初始化 /// </summary> /// <param name="services"></param> /// <param name="configure"></param> /// <returns></returns> public static IServiceCollection AddSendMessage(this IServiceCollection services, Action<MsgOptions> configure) { MsgOptions msg = new MsgOptions(); configure(msg); services.Configure(configure); services.Configure(msg.RabbitMQOptions); return services.AddSendMessage(); } }
4.定义Options类
定义好我们业务逻辑需要的配置信息Options类
查看代码
//MQ配置类 public class RabbitMQOptions { public string IP { get; set; } public string Port { get; set; } } //Msg配置类 public class MsgOptions { //是否发送 public bool SendFlag { get; set; } //MQ配置 internal Action<RabbitMQOptions>? RabbitMQOptions { get; private set; } public void Register(Action<RabbitMQOptions> action) { RabbitMQOptions = action; } } //Msg配置扩展方法,用来设置其MQ配置信息 public static class MsgOptionsExtensions { public static void UseRabbitMQ(this MsgOptions options, Action<RabbitMQOptions> configure) { if (configure == null) { throw new ArgumentNullException(nameof(configure)); } options.Register(configure); } }
5.编写Msg服务
这里我们就用打印信息的方式来表示我们将数据发送至MQ了
public interface ISendMessage { void Send(string message); } public class SendMessage : ISendMessage { private readonly RabbitMQOptions rabbitMQ; public SendMessage(IOptions<RabbitMQOptions> rabbitMQ) { this.rabbitMQ = rabbitMQ.Value; } public void Send(string message) { Console.WriteLine($"发送消息:{message},至--->{rabbitMQ.IP}:{rabbitMQ.Port}服务器"); } }
6.使用自定义中间件
我们只需要调用我们定义的扩展方法即可,博主这里用的.NET 6,使用的顶级语句。老版本的朋友就在Startup类里配置,调用方式是一样的
如下:
- Configure方法里使用,app.UseMsgSend()
- ConfigureServices方法里使用,services.AddSendMessage(x=>{...})
查看代码
using WebApplication1.MiddlewareExp; using WebApplication1.MiddlewareExp.Middleware; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //配置中间件配置信息 builder.Services.AddSendMessage(c => { c.SendFlag = true; c.UseRabbitMQ(x => { x.IP = "127.0.0.1"; x.Port = "8080"; }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } // 使用自定义的Msg中间件 app.UseMsgSend(); app.UseAuthorization(); app.MapControllers(); app.Run();
运行效果
如图,我们请求多少次,请求都会经过我们中间件。