.NET Core 自定義中間件 Middleware
引言
很多看了上一章的朋友私信部落客,問如何自定義,自己的中間件(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 //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();
運行效果
如圖,我們請求多少次,請求都會經過我們中間件。