如何通过Azure SignalR连接.NET 6 Web服务与.NET 6 WPF应用并实现客户端身份认证
看起来你遇到了几个核心问题:客户端与服务器的SignalR事件名称不匹配、Azure SignalR服务的客户端认证缺失,还有WPF特有的跨线程UI操作问题,我来一步步帮你解决:
1. 先修正事件名称不匹配的问题
你的服务器端Hub代码里调用的是SendAsync("SendMessage", id),但客户端监听的是"ReceiveMessage"——这两个名称必须完全一致!这是你收不到消息的直接原因之一。修改客户端的监听代码:
// 把"ReceiveMessage"改成和服务器一致的"SendMessage" connection.On<Guid>("SendMessage", g => ExternalLoadCompany(g));
2. 解决Azure SignalR的客户端认证问题
Azure SignalR服务默认要求客户端提供有效的访问令牌才能建立连接,不能直接用服务端点裸连。这里提供两种方案:
方案一:快速测试(不推荐生产环境)
直接在客户端使用Azure SignalR连接字符串连接,需要先安装Microsoft.Azure.SignalR.Client NuGet包,再修改连接代码:
private const string signalRConnectionString = "Endpoint=https://xxxxxxx.service.signalr.net;AccessKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;Version=1.0;"; private HubConnection connection; private Thread thread; public MainViewModel() { thread = new Thread(async () => { connection = new HubConnectionBuilder() // 使用Azure SignalR扩展方法传入连接字符串和Hub路径 .WithAzureSignalR(signalRConnectionString, "/com") .WithAutomaticReconnect() .Build(); connection.On<Guid>("SendMessage", g => ExternalLoadCompany(g)); try { await connection.StartAsync(); } catch (Exception ex) { // 处理连接异常,比如日志输出 Console.WriteLine($"SignalR连接失败: {ex.Message}"); } }); thread.Start(); }
⚠️ 注意:这个方案会把AccessKey暴露在客户端代码里,非常不安全,仅适合本地测试使用!
方案二:生产环境推荐(通过后端API获取令牌)
正确的做法是在你的.NET Web服务中添加一个接口,专门生成Azure SignalR的访问令牌,WPF客户端先调用这个接口获取令牌,再用令牌连接SignalR:
第一步:在Web服务中添加令牌生成接口
确保Web服务已安装Microsoft.Azure.SignalR NuGet包,然后添加控制器:
using Microsoft.Azure.SignalR; using Microsoft.AspNetCore.Mvc; [ApiController] [Route("api/signalr")] public class SignalRTokenController : ControllerBase { private readonly IAzureSignalRServiceManager _signalRManager; // 注入Azure SignalR服务管理器 public SignalRTokenController(IAzureSignalRServiceManager signalRManager) { _signalRManager = signalRManager; } [HttpGet("token")] public IActionResult GetClientToken(string userId = "wpf-client") { // 生成针对"/com" Hub的访问令牌,可传入用户ID用于身份标识 var accessToken = _signalRManager.GenerateClientAccessToken("/com", userId); return Ok(new { Token = accessToken }); } }
第二步:修改WPF客户端代码获取令牌并连接
private const string webApiBaseUrl = "https://你的Web服务域名"; // 替换为你的Web服务地址 private const string signalREndpoint = "https://xxxxxxx.service.signalr.net"; private HubConnection connection; private Thread thread; public MainViewModel() { thread = new Thread(async () => { // 先从Web API获取认证令牌 var accessToken = await FetchSignalRTokenAsync(); if (string.IsNullOrEmpty(accessToken)) { Console.WriteLine("获取SignalR令牌失败"); return; } connection = new HubConnectionBuilder() .WithUrl($"{signalREndpoint}/com", options => { // 设置令牌提供者 options.AccessTokenProvider = () => Task.FromResult(accessToken); }) .WithAutomaticReconnect() .Build(); connection.On<Guid>("SendMessage", g => ExternalLoadCompany(g)); try { await connection.StartAsync(); } catch (Exception ex) { Console.WriteLine($"SignalR连接失败: {ex.Message}"); } }); thread.Start(); } // 调用Web API获取令牌的方法 private async Task<string> FetchSignalRTokenAsync() { using var httpClient = new HttpClient(); var response = await httpClient.GetAsync($"{webApiBaseUrl}/api/signalr/token"); if (response.IsSuccessStatusCode) { var tokenResponse = await response.ReadFromJsonAsync<TokenResponse>(); return tokenResponse?.Token; } return null; } // 用于接收令牌的模型类 private class TokenResponse { public string Token { get; set; } }
3. 修复WPF跨线程操作UI的问题
SignalR的回调是在后台线程执行的,直接修改Companies集合会引发跨线程访问UI元素的异常,必须切换到UI线程操作:
private void ExternalLoadCompany(Guid id) { // 使用Dispatcher切换到UI线程 Application.Current.Dispatcher.Invoke(async () => { var company = await ApiCall.GetCompany(id); // 建议用await代替.Result,避免阻塞 if (company != null) { Companies.Clear(); Companies.Add(company); } }); }
另外,建议把ApiCall.GetCompany(id).Result改成await ApiCall.GetCompany(id),避免线程阻塞的问题。
内容的提问来源于stack exchange,提问作者Raymond Osterbrink

