使用Azure SignalR向特定用户发送消息

10

在使用Azure SignalR(而非.Net Core SignalR)时,是否支持使用Clients.Users.SendAsync向特定用户发送消息?

[Authorize]
public class ChatHub : Hub
{
    public async Task OnNewMessage(string userId, string message)
    {
        await Clients.Users(userId) 
             //Does Azure SignalR supports sending message to specific user,
             // the way .Net Core SignalR does it
            .SendAsync("OnNewMessage", userId, message);
    }
}

如果是的话,Asp.Net Identity Claims Principal如何传递给Azure SignalR?此问题涉及SignalR如何处理.Users(<*some-user-id*>).Send()。使用.Net Core SignalR时,.User(userId).Send()效果完美,因此我认为SignalR SDK必须在内存中保留每个连接ID和用户ID之间的映射关系。Azure SignalR是否内部跟踪UserID、Connection ID的映射并发送消息呢?注意:本人已经按照文档自定义了UserIdBasedProvider等内容。

1
顺便说一下,“广播”这个术语不应用于单播消息。 - Dai
我认为你需要在这里继承IPrincipal类,然后你就可以访问用户的身份。 - Harkirat singh
1
附注:我强烈建议在扩展场景中使用组而不是连接ID和标识,这样通常效果更好。https://consultwithgriff.com/signalr-connection-ids/ - Kevin Griffin
1
但是,如果您使用Azure SignalR服务,则会使用WebSocketsHubLifetimeManager,它只是将连接管理转移到Azure SignalR服务,这可以保证在多服务器场景下正常工作。 Azure SignalR服务如何管理连接取决于他们,但我认为他们内部使用RedisHubLifetimeManager,因为它支持多服务器场景。 - Alexander Mokin
@Abhijeet 只需找到入口点(即类和方法名称),然后下载库代码并使用 Resharper 进行代码导航以深入挖掘。或者,如果您不想下载代码,github 现在也支持代码导航,但它不如 Resharper 好用。有时我还需要使用 dotpeek(如果源代码不可用)或检查调用堆栈以查找正在使用的方法和类。 - Alexander Mokin
显示剩余5条评论
2个回答

2

如果您想实现无服务器架构,您应该使用Azure Functions。典型的无服务器架构将如下所示。

azure signalr architecture

客户端(即浏览器)向Azure函数发送协商请求,响应包含建立与Azure SignalR连接所需的所有信息。您应该将JWT令牌附加到协商请求中。如何获取JWT令牌由您决定。附加它非常简单,只需在创建SignalR连接时提供令牌工厂即可。
const connection = new signalR.HubConnectionBuilder()
    .withUrl('link to your azure functions api', {
        accessTokenFactory: () => {
            return 'your token'
        }
    })
    .build();

为了使此功能正常工作,您还需要设置一个upstream连接,以便您的Azure SignalR服务可以将所有事件转发到您的Azure函数。Azure函数处理上游事件并向Azure SignalR发送请求,以向给定用户或多个用户广播数据。

upstream set up

hubSendMessage方法的代码与常规的hub非常相似,除了Azure函数绑定。您还需要扩展ServerlessHub类而不是Hub,并安装Microsoft.Azure.WebJobs.Extensions.SignalRService包。

public class ChatHub : ServerlessHub
{

    [FunctionName(nameof(SendToUser))]
    public async Task SendToUser([SignalRTrigger] InvocationContext invocationContext, string userName, string message)
    {
        await Clients.User(userName).SendAsync("new_message", new NewMessage(invocationContext, message));
    }

    [FunctionName("negotiate")]
    public SignalRConnectionInfo Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req)
    {
        var claims = GetClaims(req.Headers["Authorization"]);
        var userName = claims.First(x => x.Type == ClaimTypes.NameIdentifier);
        return Negotiate(userName.Value, claims);
    }


    private class NewMessage
    {
        public string ConnectionId { get; }
        public string Sender { get; }
        public string Text { get; }

        public NewMessage(InvocationContext invocationContext, string message)
        {
            Sender = string.IsNullOrEmpty(invocationContext.UserId) ? string.Empty : invocationContext.UserId;
            ConnectionId = invocationContext.ConnectionId;
            Text = message;
        }
    }
}

你可以在这里找到一份带有逐步说明的代码示例。

编辑:

如果你不想完全使用无服务器架构,而是想使用常规的 Web 应用程序,那也完全没问题。架构图将如下所示。

Web app architecture

在这种情况下,您应该使用常规的hub类。您发布的代码应该可以正常工作。
连接管理完全由Azure Signalr服务完成,您不需要进行任何额外的同步,也不需要将连接数据存储在数据库中。只需设置层大小和单位数量。
以下是它如何工作的简短描述:
1.客户端(例如浏览器中的Js客户端)向Web应用程序发送请求到negotiate端点,并接收到指向Azure Signalr服务和访问令牌的链接。这是一个JWT令牌。 JWT令牌由几个部分组成。该令牌的第二部分是有效负载,其中包含声明。 2.使用从negotiate端点获取的数据,客户端连接到Azure Signalr服务。
因此,Azure SignalR服务使用从协商端点接收到的访问令牌来验证用户。您不必为此编写任何其他代码,库已经在后台完成了所有操作。
如果您使用Web应用程序,则SignalR SDK处理negotiate请求,您不需要为此编写任何其他代码。

这些都在本文中有详细描述。

你可以在这里找到有关实现身份验证的详细指南。


谢谢。您是否意思是说无法使用常规的带有Asp.Net Identity 的Web应用程序完成此操作? - Abhijeet
如果您不想完全使用无服务器实现应用程序,使用Web应用程序是完全可以的。我已经更新了帖子,提供了更多细节。 - Alexander Mokin
谢谢,你能回答第二部分吗?“Asp.Net Identity Claims Principal 如何传递到 Azure SignalR?” - Abhijeet
协商请求处理程序生成JWT访问令牌,然后将其传递给Azure SignalR服务。 JWT令牌由几个部分组成。该令牌的第二部分是有效载荷,其中包含声明。 - Alexander Mokin
1
@Abhijeet 如果你想知道JS客户端中如何实现协商,可以在这里找到相关代码,协商终结点的代码在这里。如果你想了解更多关于JWT令牌结构的信息,请查看此链接 - Alexander Mokin
我会将您的答案标记为正确的,在此之前需要验证一些东西,可以吗? - Abhijeet

0

您可以通过实现自定义的IUserIdProvider并将其注册为单例来实现此目的。

 public class UserIdProvider : IUserIdProvider
    {
        public string GetUserId(HubConnectionContext connection)
        {
            return connection.User.FindFirst(ClaimConstants.PreferredUserName).Value;
        }
    }

相关 参考资料Github


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接