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

Blazor 8通知服务实现报错:Connection closed with an error

问题:Blazor 8通知服务SignalR报错及轮询方案异常

一、问题背景

使用Blazor 8开发通知服务,需求是从任意Razor页面推送消息至MainLayout页面更新通知计数,但持续触发framework/blazor.server.js报错:Server returned an error on close: Connection closed with an error;同时登录用户后页面陷入无限循环。

二、完整报错追踪

Completed connection handshake. Using HubProtocol 'blazorpack'.
dbug: Microsoft.AspNetCore.SignalR.HubConnectionHandler[2]
      Error when processing requests.
      System.IO.InvalidDataException: Reading 'target' as String failed.
       ---> System.MissingMethodException: Method not found: 'System.String Microsoft.AspNetCore.SignalR.IInvocationBinder.GetTarget(System.ReadOnlySpan`1<Byte>)'.
         at Microsoft.AspNetCore.SignalR.Protocol.MessagePackHubProtocolWorker.ReadString(MessagePackReader& reader, IInvocationBinder binder, String field)
         --- End of inner exception stack trace ---
         at Microsoft.AspNetCore.SignalR.Protocol.MessagePackHubProtocolWorker.ReadString(MessagePackReader& reader, IInvocationBinder binder, String field)
         at Microsoft.AspNetCore.SignalR.Protocol.MessagePackHubProtocolWorker.CreateInvocationMessage(MessagePackReader& reader, IInvocationBinder binder, Int32 itemCount)
         at Microsoft.AspNetCore.SignalR.Protocol.MessagePackHubProtocolWorker.TryParseMessage(ReadOnlySequence`1& input, IInvocationBinder binder, HubMessage& message)
         at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.DispatchMessagesAsync(HubConnectionContext connection)
         at Microsoft.AspNetCore.SignalR.HubConnectionHandler`1.RunHubAsync(HubConnectionContext connection)

三、当前实现代码

Program.cs 配置

builder.Services.AddSingleton<IDataAccess, DataAccess>(); 
builder.Services.AddSingleton<INotificationService, NotificationService>(); // 添加NotificationService

// Identity配置
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
                .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<IdentityUser>>();
builder.Services.AddSignalR(e =>
            {
                e.EnableDetailedErrors = true;
                e.MaximumReceiveMessageSize = 102400000;
            });

MainLayout.razor 核心逻辑

@using Microsoft.AspNetCore.Components.Authorization

@inject AuthenticationStateProvider AuthenticationStateProvider
@inject INotificationService NotificationService
@inject IJSRuntime JS
@inject NavigationManager Navigation

 private int NotificationCount = 0; // 通知数量(初始为0)
    private string userId;

    private bool ShowNotificationPopup { get; set; } = false;

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            userId = user.FindFirst(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
            await PollNotifications();
        }
    }

    private void ToggleNotificationPopup()
    {
        // 切换通知弹窗显示状态
        ShowNotificationPopup = !ShowNotificationPopup;
    }

    private async Task PollNotifications()
    {
        while (true)
        {
            await Task.Delay(5000); // 每5秒轮询一次
            var notifications = await NotificationService.GetUnreadNotificationsAsync(userId);
            NotificationCount = notifications.Count;
            StateHasChanged();
        }
    }

四、解决方案

1. 修复SignalR版本兼容问题

报错中的MissingMethodException是典型的版本不匹配问题,Blazor 8依赖的SignalR相关包必须与ASP.NET Core 8版本完全一致:

  • 检查所有Microsoft.AspNetCore.SignalR相关NuGet包(包括Server、Client、MessagePack)版本,确保均为8.x.x
  • 清理项目bin/obj目录,重新生成解决方案

2. 解决轮询导致的无限循环问题

当前PollNotifications中的while(true)会阻塞组件生命周期,导致页面无响应,改用Timer替代:

private Timer _notificationTimer;

protected override async Task OnInitializedAsync()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;

    if (user.Identity.IsAuthenticated)
    {
        userId = user.FindFirst(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
        // 初始化Timer,立即执行一次,之后每5秒执行
        _notificationTimer = new Timer(async _ => await UpdateNotificationCount(), null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
    }
}

private async Task UpdateNotificationCount()
{
    var notifications = await NotificationService.GetUnreadNotificationsAsync(userId);
    // 在Blazor线程上下文更新UI
    await InvokeAsync(() =>
    {
        NotificationCount = notifications.Count;
        StateHasChanged();
    });
}

// 组件销毁时释放Timer资源
public void Dispose()
{
    _notificationTimer?.Dispose();
}

注意:需让MainLayout实现IDisposable接口,确保组件销毁时释放Timer。

3. 轻量替代SignalR的推送方案

若不想依赖SignalR,可使用IAsyncEnumerable结合Blazor的StreamRendering实现实时更新:

  • 在NotificationService中添加流式获取通知计数的方法:
public async IAsyncEnumerable<int> GetUnreadNotificationCountStream(string userId)
{
    while (true)
    {
        await Task.Delay(5000);
        var count = (await GetUnreadNotificationsAsync(userId)).Count;
        yield return count;
    }
}
  • 在MainLayout中接收流:
@attribute [StreamRendering(true)]

// 其他代码不变...

protected override async Task OnInitializedAsync()
{
    var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
    var user = authState.User;

    if (user.Identity.IsAuthenticated)
    {
        userId = user.FindFirst(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value;
        await foreach (var count in NotificationService.GetUnreadNotificationCountStream(userId))
        {
            NotificationCount = count;
            StateHasChanged();
        }
    }
}

内容的提问来源于stack exchange,提问作者Benoît S

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:55:57