3

.NET Core 自定义中间件 Middleware

 2 years ago
source link: https://www.cnblogs.com/Bob-luo/p/15795009.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

很多看了上一章的朋友私信博主,问如何自定义,自己的中间件(Middleware),毕竟在实际的项目中,大家会有很多需求要用到中间件,比如防盗链、缓存、日志等等功能,于是博主这边就简单讲解一下框架、组件惯用的优雅手法,官方也推荐这种写法,这样会使得我们扩展性更好,也不会破坏原本结构。

什么是中间件

中间件是一种装配到应用管道以处理请求响应的软件。 每个组件:

  • 选择是否将请求传递到管道中的下一个组件。
  • 可在管道中的下一个组件前后执行工作。

使用 RunMap 和 Use 扩展方法来配置请求委托,请求委托用于生成请求管道。 请求委托处理每个 HTTP 请求。

简单的说,我们按需求决定使用哪些组件,程序运行时,一个HTTP请求过来,程序执行流程,是按照我们定义的组件顺序执行的。所以我们项目上的中间件放置顺序是不能乱的,并且不用的也不要装配,避免消耗性能。

如果想做其他相关了解,博主建议直接在官网上看文档,微软的文档写的还是很好的,还提供的Demo下载,比很多网上博客讲的好。

微软官网地址:ASP.NET Core 中间件 | Microsoft Docs

接下来进入正题,我们写一个,把所有http请求地址发送到MQ的中间件:

1.编写MsgMiddleware类

这里我们就使用直接编写Middleware类的方式,大家也可以使用实现IMiddleware接口的方式。两种底层原理不一样:前者是框架启动时就实例化;后者是请求来时才实例化,用完立即释放。

编写Middleware类注意:

  1. 编写好InvokeAsync或者Invoke方法
  2. 构造函数参数需要一个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类里配置,调用方式是一样的

  1. Configure方法里使用,app.UseMsgSend()
  2. 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();

如图,我们请求多少次,请求都会经过我们中间件。

2696180-20220113211435625-610530697.png


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK