ASP.NET Core 中介軟體
ASP.NET Core的處理流程是一個管道,而中介軟體是裝配到管道中的用於處理請求和響應的元件。中介軟體按照裝配的先後順序執行,並決定是否進入下一個元件。中介軟體管道的處理流程如下圖(圖片來源於官網):
管道式的處理方式,更加方便我們對程式進行擴充套件。
使用中介軟體
ASP.NET Core中介軟體模型是我們能夠快捷的開發自己的中介軟體,完成對應用的擴充套件,我們先從一個簡單的例子瞭解一下中介軟體的開發。
Run
首先,我們建立一個ASP.NET Core 應用,在Startup.cs中有如下程式碼:
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
這段程式碼中,使用Run方法執行一個委託,這就是最簡單的中介軟體,它攔截了所有請求,返回一段文字作為響應。Run委託終止了管道的執行,因此也叫作終端中介軟體。
Use
我們再看另外一個例子:
app.Use(async (context, next) =>
{
//Do something here
//Invoke next middleware
await next.Invoke();
//Do something here
});
這段程式碼中,使用Use方法執行一個委託,我們可以在Next呼叫之前和之後分別執行自定義的程式碼,從而可以方便的進行日誌記錄等工作。這段程式碼中,使用next.Invoke()方法呼叫下一個中介軟體,從而將中介軟體管道連貫起來;如果不呼叫next.Invoke()方法,則會造成管道短路。
Map和MapWhen
處理上面兩種方式,ASP.NET Core 還可以使用Map建立基於路徑匹配的分支、使用MapWhen建立基於條件的分支。程式碼如下:
private static void HandleMap(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Handle Map");
});
}
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Map("/map", HandleMap);
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello World!");
});
}
上面的程式碼演示瞭如何使用Map和MapWhen建立基於路徑和條件的分支。另外,Map方法還支援層級的分支,我們參照下面的程式碼:
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
需要注意,使用 Map 時,將從 HttpRequest.Path 中刪除匹配的Path,並針對每個請求將該線段追加到 HttpRequest.PathBase。例如對於路徑/level1/level2a
,當在level1App中進行處理時,它的請求路徑被截斷為/level2a
,當在level2AApp中進行處理時,它的路徑就變成/
了,而相應的PathBase會變為/level1/level2a
。
開發中介軟體
看到這裡,我們已經知道中介軟體的基本用法,是時候寫一個真正意義的中介軟體了。
基於約定的中介軟體開發
在 ASP.NET Core 官網上面提供了一個簡單的例子,通過中介軟體來設定應用的區域資訊,程式碼如下:
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
return next();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
通過這段程式碼,我們可以通過QueryString的方式設定應用的區域資訊。但是這樣的程式碼怎樣複用呢?注意,中介軟體一定要是可複用、方便複用的。我們來改造這段程式碼:
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
//......
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}
這裡定義一個委託,用於執行具體的業務邏輯,然後在Configure中呼叫這個委託:
app.UseMiddleware<RequestCultureMiddleware>();
這樣還是不太方便,不像我們使用app.UseMvc()這麼方便,那麼我們來新增一個擴充套件方法,來實現更方便的複用:
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
然後我們就可以這樣使用中介軟體了:
app.UseRequestCulture();
通過委託構造中介軟體,應用程式在執行時建立這個中介軟體,並將它新增到管道中。這裡需要注意的是,中介軟體的建立是單例的,每個中介軟體在應用程式生命週期內只有一個例項。那麼問題來了,如果我們業務邏輯需要多個例項時,該如何操作呢?請繼續往下看。
基於請求的依賴注入
通過上面的程式碼我們已經知道了如何編寫一箇中介軟體,如何方便的複用這個中介軟體。在中介軟體的建立過程中,容器會為我們建立一箇中介軟體例項,並且整個應用程式生命週期中只會建立一個該中介軟體的例項。通常我們的程式不允許這樣的注入邏輯。
其實,我們可以把中介軟體理解成業務邏輯的入口,真正的業務邏輯是通過Application Service層實現的,我們只需要把應用服務注入到Invoke方法中即可。
ASP.NET Core為我們提供了這種機制,允許我們按照請求進行依賴的注入,也就是每次請求建立一個服務。程式碼如下:
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
// IMyScopedService is injected into Invoke
public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
{
svc.MyProperty = 1000;
await _next(httpContext);
}
}
在這段程式碼中,CustomMiddleware的例項仍然是單例的,但是IMyScopedService是按照請求進行注入的,每次請求都會建立IMyScopedService的例項,svc物件的生命週期是PerRequest的。
基於約定的中介軟體模板
這裡提供一個完整的示例,可以理解為一箇中介軟體的開發模板,方便以後使用的時候參考。整個過程分以下幾步:
- 將業務邏輯封裝到ApplicationService中
- 建立中介軟體代理類
- 建立中介軟體擴充套件類
- 使用中介軟體
程式碼如下:
namespace MiddlewareDemo
{
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
//1.定義並實現業務邏輯
public interface IMyScopedService
{
int MyProperty { get; set; }
}
public class MyScopedService : IMyScopedService
{
public int MyProperty { get; set; }
}
//2.建立中介軟體代理類
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
// IMyScopedService is injected into Invoke
public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
{
svc.MyProperty = 1000;
await _next(httpContext);
}
}
}
//3.1 新增依賴服務註冊
namespace Microsoft.Extensions.DependencyInjection
{
using MiddlewareDemo;
public static partial class CustomMiddlewareExtensions
{
/// <summary>
/// 新增服務的依賴註冊
/// </summary>
public static IServiceCollection AddCustom(this IServiceCollection services)
{
return services.AddScoped<IMyScopedService, MyScopedService>();
}
}
}
//3.2 建立中介軟體擴充套件類
namespace Microsoft.AspNetCore.Builder
{
using MiddlewareDemo;
public static partial class CustomMiddlewareExtensions
{
/// <summary>
/// 使用中介軟體
/// </summary>
public static IApplicationBuilder UseCustom(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
}
//4. 使用中介軟體
public void ConfigureServices(IServiceCollection services)
{
services.AddCustom();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCustom();
}
基於工廠啟用的中介軟體
我們前面介紹的中介軟體開發都是基於約定的,可以讓我們快速上手進行開發。同時ASP.NET Core還提供了基於工廠啟用的中介軟體開發方式,我們可以通過實現IMiddlewareFactory、IMiddleware介面進行中介軟體開發。
public class FactoryActivatedMiddleware : IMiddleware
{
private readonly AppDbContext _db;
public FactoryActivatedMiddleware(AppDbContext db)
{
_db = db;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
var keyValue = context.Request.Query["key"];
if (!string.IsNullOrWhiteSpace(keyValue))
{
_db.Add(new Request()
{
DT = DateTime.UtcNow,
MiddlewareActivation = "FactoryActivatedMiddleware",
Value = keyValue
});
await _db.SaveChangesAsync();
}
await next(context);
}
}
上面這段程式碼演示瞭如何使用基於工廠啟用的中介軟體,在使用過程中有兩點需要注意:1.需要在ConfigureServices中進行服務註冊;2.在UseMiddleware()方法中不支援傳遞引數。