Blazor Server场景下如何将类文件数据传递到Razor页面
实现方案
你需要用「单例中间件+事件通知」的模式实现需求,完全不需要修改Server类的原有业务逻辑,中间件作为纯中转层解耦服务端和UI:
步骤1:注册全局单例服务
首先在Program.cs中注册中间件和TCP服务为全局单例,保证整个程序只有一份实例,数据全局统一:
// 其他服务注册逻辑保留 builder.Services.AddSingleton<Middleware>(); builder.Services.AddSingleton(sp => new Server(8600, sp.GetRequiredService<Middleware>())); var app = builder.Build(); // 在这里启动TCP服务,不要放在页面里启动,避免多用户访问重复启动 var server = app.Services.GetRequiredService<Server>(); new Thread(() => server.Start()).Start(); // 原有app.Run()等逻辑保留
步骤2:修改中间件类,新增事件通知
中间件内部存储UI需要的设备数据,同时定义事件通知订阅者(也就是Razor页面)更新:
public class Middleware { // 存储UI需要的设备列表,直接给页面读取 public List<DeviceInfo> ConnectedDevices { get; } = new(); // 设备添加事件,新设备接入时触发 public event Action? OnDeviceChanged; // 错误通知事件,服务端报错时触发 public event Action<string>? OnErrorOccurred; public void AddDeviceEntry(string device, string endpoint) { var ipPort = endpoint.Split(':'); ConnectedDevices.Add(new DeviceInfo { DeviceName = device, Ip = ipPort[0], Port = ipPort[1] }); // 通知所有订阅者数据更新 OnDeviceChanged?.Invoke(); } public void ErrorMessage(string msg) { OnErrorOccurred?.Invoke(msg); } } // 设备信息DTO,解耦UI和TCP服务逻辑 public class DeviceInfo { public string DeviceName { get; set; } = string.Empty; public string Ip { get; set; } = string.Empty; public string Port { get; set; } = string.Empty; }
步骤3:修改Index.razor,订阅中间件事件
页面只依赖中间件,不直接访问Server类,收到事件通知后调度到UI线程刷新界面:
@page "/" @inject Middleware Mid @implements IDisposable @* 错误提示区域 *@ @if (!string.IsNullOrEmpty(ErrorMsg)) { <div class="alert alert-danger mt-2">@ErrorMsg</div> } <table class="table"> <thead> <tr> <th scope="col">Dispositivos</th> <th scope="col">IP</th> <th scope="col">Porta</th> </tr> </thead> <tbody> <tr> <td>Servidor</td> <td>127.0.0.1</td> <td>8600</td> </tr> @foreach (var device in Mid.ConnectedDevices) { <tr> <td>@device.DeviceName</td> <td>@device.Ip</td> <td>@device.Port</td> </tr> } </tbody> </table> @code { private string ErrorMsg { get; set; } = string.Empty; protected override void OnInitialized() { // 订阅设备更新事件 Mid.OnDeviceChanged += () => { // 后台线程触发的事件必须调度到UI同步上下文再刷新 InvokeAsync(StateHasChanged); }; // 订阅错误通知事件 Mid.OnErrorOccurred += msg => { InvokeAsync(() => { ErrorMsg = msg; StateHasChanged(); }); }; } // 页面销毁时取消订阅,避免内存泄漏 public void Dispose() { Mid.OnDeviceChanged -= () => InvokeAsync(StateHasChanged); Mid.OnErrorOccurred -= _ => InvokeAsync(StateHasChanged); } }
原理说明
- 完全符合你要求的三层结构:Server类→中间件→UI页面,Server不需要感知UI存在,UI也不依赖Server的内部实现
- 后台线程触发的事件必须通过
InvokeAsync调度到Blazor的UI同步上下文,否则直接调用StateHasChanged会报线程错误 - 事件模式天然支持多页面订阅,如果后续需要其他页面也接收设备/错误通知,直接订阅中间件的对应事件即可
内容的提问来源于stack exchange,提问作者jeanvr
相关产品推荐
相关产品推荐

