.NET Core 自定義中介軟體 Middleware

極客Bob發表於2022-01-18

引言

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

執行效果

如圖,我們請求多少次,請求都會經過我們中介軟體。

.NET Core 自定義中介軟體 Middleware

相關文章