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

如何通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 17:57:31