查看原文
其他

.NET Core 自定义中间件 Middleware

DotNet 2022-07-19

↓推荐关注↓

‍前言

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

什么是中间件

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

  • 选择是否将请求传递到管道中的下一个组件。

  • 可在管道中的下一个组件前后执行工作。


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

简单的说,我们按需求决定使用哪些组件,程序运行时,一个HTTP请求过来,程序执行流程,是按照我们定义的组件顺序执行的。

所以我们项目上的中间件放置顺序是不能乱的,并且不用的也不要装配,避免消耗性能。

概念图

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

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

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

一、编写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);
    }
}

二、编写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));
    }
}

三、编写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();
    }
}

四、定义Options类

定义好我们业务逻辑需要的配置信息Options类

//MQ配置类
public class RabbitMQOptions
{
    public string IP { getset; }
    public string Port { getset; }
}
//Msg配置类
public class MsgOptions
{
    //是否发送
    public bool SendFlag { getset; }
    //MQ配置
    internal Action<RabbitMQOptions>? RabbitMQOptions { getprivate 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);
    }
}

五、编写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}服务器");
    }
}

六、使用自定义中间件

我们只需要调用我们定义的扩展方法即可,博主这里用的.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();

运行效果

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

转自:极客Bob

链接:cnblogs.com/Bob-luo/p/15795009.html

- EOF -

推荐阅读  点击标题可跳转
C# 生成二维码方法(QRCoder)C# 实现网页截取长图 Playwright版理解ASP.NET Core - Cookie 的身份认证

看完本文有收获?请转发分享给更多人

推荐关注「DotNet」,提升.Net技能 

点赞和在看就是最大的支持❤️

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存