Blazor中间件如何仅拦截页面请求,避免影响Websocket与API?
问题描述
我正在Blazor应用中编写一个PowerMiddleWare中间件,用于检测系统是否处于休眠状态,若是则返回静态HTML文件。中间件代码如下:
public class PowerMiddleWare { private readonly RequestDelegate next; private readonly PowerButtonWatcher powerButton; private readonly ILogger<PowerMiddleWare> logger; public PowerMiddleWare( RequestDelegate next, PowerButtonWatcher powerButton, ILogger<PowerMiddleWare> logger) { this.next = next; this.powerButton = powerButton; this.logger = logger; } public async Task InvokeAsync(HttpContext context) { if (**LOGIC HERE** && powerButton.IsSleeping) { string filepath = "Pages/sleeping.html"; context.Response.ContentType = "text/html"; context.Response.StatusCode = 200; await context.Response.SendFileAsync(filepath); } else { await next.Invoke(context); } } }
目前的问题是:所有Websocket和API请求都会经过该中间件,当系统休眠时,这些请求也会返回上述HTML页面,这并非预期结果。我希望仅过滤页面请求,但不想通过统一路径前缀(如"/page/...")的方式实现,因为应用规模较大。请问是否可以借助@page指令来实现该需求?
解决方案
可以借助Blazor的路由系统实现,核心思路是提前收集所有带@page指令的Blazor页面路由模板,在中间件中判断当前请求是否匹配这些模板,具体步骤如下:
1. 收集所有Blazor页面的路由
通过反射扫描当前程序集,提取所有Blazor组件上的RouteAttribute(对应@page指令),整理出路由模板列表:
public static class BlazorRouteCollector { public static List<string> GetBlazorPageRoutes(Assembly assembly) { var routeTemplates = new List<string>(); // 筛选所有Blazor组件类型 var componentTypes = assembly.GetTypes() .Where(t => typeof(ComponentBase).IsAssignableFrom(t) && !t.IsAbstract); foreach (var type in componentTypes) { // 获取组件上的所有@page路由模板 var routeAttributes = type.GetCustomAttributes<RouteAttribute>(); foreach (var attr in routeAttributes) { routeTemplates.Add(attr.Template); } } return routeTemplates; } }
2. 修改中间件实现路由匹配
在中间件中注入收集到的路由模板列表,使用Blazor内置的TemplateMatcher判断请求是否属于Blazor页面:
public class PowerMiddleWare { private readonly RequestDelegate next; private readonly PowerButtonWatcher powerButton; private readonly ILogger<PowerMiddleWare> logger; private readonly List<TemplateMatcher> _pageMatchers; public PowerMiddleWare( RequestDelegate next, PowerButtonWatcher powerButton, ILogger<PowerMiddleWare> logger, List<string> blazorPageRoutes) { this.next = next; this.powerButton = powerButton; this.logger = logger; // 为每个路由模板创建匹配器 _pageMatchers = blazorPageRoutes.Select(template => new TemplateMatcher(TemplateParser.Parse(template), new Dictionary<string, object>())).ToList(); } public async Task InvokeAsync(HttpContext context) { bool isBlazorPageRequest = false; var requestPath = context.Request.Path.Value?.TrimStart('/') ?? string.Empty; // 遍历匹配所有页面路由 foreach (var matcher in _pageMatchers) { if (matcher.TryMatch(requestPath, new Dictionary<string, object>())) { isBlazorPageRequest = true; break; } } // 仅对Blazor页面请求返回休眠HTML if (isBlazorPageRequest && powerButton.IsSleeping) { // 注意拼接完整文件路径,避免找不到文件 string filepath = Path.Combine(Directory.GetCurrentDirectory(), "Pages/sleeping.html"); context.Response.ContentType = "text/html"; context.Response.StatusCode = 200; await context.Response.SendFileAsync(filepath); } else { await next.Invoke(context); } } }
3. 注册服务与中间件
在Program.cs中收集路由并注入,同时将中间件注册到请求管道(注意要放在Blazor路由中间件之前):
var builder = WebApplication.CreateBuilder(args); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); // 收集当前程序集的Blazor页面路由 var blazorRoutes = BlazorRouteCollector.GetBlazorPageRoutes(Assembly.GetExecutingAssembly()); builder.Services.AddSingleton(blazorRoutes); // 注册中间件依赖 builder.Services.AddScoped<PowerMiddleWare>(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseAntiforgery(); // 注册PowerMiddleWare app.UseMiddleware<PowerMiddleWare>(); app.MapRazorComponents<App>() .AddInteractiveServerRenderMode(); app.Run();
额外优化:快速排除非页面请求
可以在中间件中先直接排除API、Websocket这类明确非页面的请求,减少路由匹配的开销:
public async Task InvokeAsync(HttpContext context) { // 直接排除API请求 var isApiRequest = context.Request.Path.StartsWithSegments("/api"); // 直接排除Websocket请求 var isWebSocketRequest = context.Request.Headers.ContainsKey("Upgrade") && context.Request.Headers["Upgrade"].Equals("websocket", StringComparison.OrdinalIgnoreCase); if (isApiRequest || isWebSocketRequest) { await next.Invoke(context); return; } // 后续的Blazor页面路由匹配逻辑... }
内容的提问来源于stack exchange,提问作者Frixer
相关产品推荐
相关产品推荐

