中间件的注册除了可以借助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注册的中间件先执行。对于两个采用相同方式注册的中间件,先被注册的中间会先执行。