引言
很多看了上一章的朋友私信博主,問如何自定義,自己的中介軟體(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 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();
執行效果
如圖,我們請求多少次,請求都會經過我們中介軟體。