.NET Core 自定義中間件 Middleware

引言

很多看了上一章的朋友私信博主,問如何自定義,自己的中間件(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 //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();

運行效果

如圖,我們請求多少次,請求都會經過我們中間件。