You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 21:53:21