中間件的注冊除了可以借助Startup對象(DelegateStartup或者ConventionBasedStartup)來完成之外,也可以利用另一個叫做StartupFilter的對象來實現。所謂的StartupFilter是對所有實現了IStartupFilter接口的類型及其對象的統稱。IStartupFilter接口定義了如下一個唯一的方法Configure,該方法的參數next返回的Action<IApplicationBuilder>對象體現了後續StartupFilter和Startup對中間件的注冊,而自身對中間件的注冊則實現在返回的Action<IApplicationBuilder>對象中。[本文已經同步到《ASP.NET Core框架揭秘》之中]
1: public interface IStartupFilter
2: {
3: Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
4: }
我們可以采用服務注冊的方式注冊多個StartupFilter。具體來說,StartupFilter具有如下兩種不同的注冊方式,一種是通過調用WebHostBuilder的ConfigureServices方法以服務的形式注冊所需的StartupFilter,另一種則是將針對StartupFilter的服務注冊實現在啟動類的ConfigureServices方法上。
1: //注冊方式1
2: new WebHostBuilder()
3: .ConfigureServices(svcs => svcs
4: .AddSingleton<IStartupFilter, Filter1>()
5: .AddSingleton<IStartupFilter, Filter2>())
6: …
7:
8: //注冊方式2
9: public class Startup
10: {
11: public void ConfigureServices(IServiceCollection svcs)
12: {
13: svcs.AddSingleton<IStartupFilter,Filter1>()
14: .AddSingleton<IStartupFilter, Filter2>();
15: }
16: }
既然中間件可以同時通過Startup和StartupFilter進行注冊,那麼通過這兩個種方式注冊的中間件有何不同嗎?其實它們唯一的區別在於StartupFilter注冊的中間件會先執行。話句話說,對於由注冊中間件構成的管道來說,通過Startup注冊的中間件位於通過StartupFilter注冊的中間件之後。我們不妨通過一個簡單的實例來證實這一點。我們在一個ASP.NET Core控制台應用中定義如下四個中間件類型(Foo、Bar、Baz和Gux),它們針對請求的處理邏輯很簡單,就是將自身的類型名稱寫入請求的響應中。
1: public abstract class MiddlewareBase
2: {
3: private RequestDelegate _next;
4:
5: public MiddlewareBase(RequestDelegate next)
6: {
7: _next = next;
8: }
9: public async Task Invoke(HttpContext context)
10: {
11: await context.Response.WriteAsync($"{this.GetType().Name}=>");
12: await _next(context);
13: }
14: }
15:
16: public class Foo : MiddlewareBase
17: {
18: public Foo(RequestDelegate next) : base(next){}
19: }
20: public class Bar : MiddlewareBase
21: {
22: public Bar(RequestDelegate next) : base(next) {}
23: }
24: public class Baz : MiddlewareBase
25: {
26: public Baz(RequestDelegate next) : base(next) {}
27: }
28: public class Gux : MiddlewareBase
29: {
30: public Gux(RequestDelegate next) : base(next) {}
31: }
接下來我們定義了如下一個泛型的 StartupFilter<TMiddleware>類,這是一個專門用於注冊指定類型中間件的StartupFilter,泛型參數代表注冊的中間件類型。在實現的Configure方法中,我們將中間件的注冊實現在返回的Action<IApplicationBuilder>對象中。
1: public class StartupFilter<TMiddleware> : IStartupFilter
2: {
3: public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
4: {
5: return app=> {
6: app.UseMiddleware<TMiddleware>();
7: next(app);
8: };
9: }
10: }
我們最終編寫如下一段簡單的程序來啟動承載的應用程序。如下面的額代碼片段所示,在利用WebHostBuilder創建並啟動WebHost之前,我們調用其ConfigureServices方法注冊了兩個StartupFilter<TMiddleware>對象,它們對應的中間件類型分別為Foo和Bar。在隨後調用的Configure方法中,我們又完成了針對中間Baz和Gux的注冊。這段程序實際上注冊了五個中間件(調用ApplicationBuilder的Run方法可以視為中間件注冊)。
1: public class Program
2: {
3: public static void Main()
4: {
5: new WebHostBuilder()
6: .UseKestrel()
7: .ConfigureServices(svcs => svcs
8: .AddSingleton<IStartupFilter>(new StartupFilter<Foo>())
9: .AddSingleton<IStartupFilter>(new StartupFilter<Bar>()))
10: .Configure(app => app
11: .UseMiddleware<Baz>()
12: .UseMiddleware<Gux>()
13: .Run(async context=> await context.Response.WriteAsync("End")))
14: .Build()
15: .Run();
16: }
17: }
我們現在需要確定注冊的這五個在進行請求處理過程中的執行順序。為此我們直接啟動這個程序,然後開啟浏覽器訪問默認的監聽地址(http://localhost:5000),浏覽器會按照如下圖所示形式顯示出請求在這個五個中間件中的“路由”。浏覽器顯示的結果清晰地表明通過StartupFilter注冊的中間件比通過Startup注冊的中間件先執行。對於兩個采用相同方式注冊的中間件,先被注冊的中間會先執行。