SignalR - 使用(IUserIdProvider) *NEW 2.0.0* 向特定用户发送消息

123
在最新版的Asp.Net SignalR中,添加了一种使用"IUserIdProvider"接口向特定用户发送消息的新方法。
public interface IUserIdProvider
{
   string GetUserId(IRequest request);
}

public class MyHub : Hub
{
   public void Send(string userId, string message)
   {
      Clients.User(userId).send(message);
   }
}

我的问题是:我如何知道我要将消息发送给谁?这种新方法的解释非常肤浅。而且SignalR 2.0.0的草案声明中存在此错误,无法编译。有人实现了这个功能吗?

更多信息:http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider

拥抱。


1
你需要研究一下使用SignalR进行身份验证和授权。UserId将成为IPrincipal提供程序的一部分。 - Gjohn
6个回答

176

SignalR为每个连接提供ConnectionId。要找出哪个连接属于谁(用户),我们需要在连接和用户之间创建映射。这取决于您如何在应用程序中标识用户。

在SignalR 2.0中,使用内置的IPrincipal.Identity.Name来完成此操作,它是在ASP.NET身份验证期间设置的已登录用户标识符。

但是,您可能需要使用与Identity.Name不同的标识符将连接与用户进行映射。为此,可以使用此新提供程序与自定义实现一起使用,以实现将用户与连接进行映射。

使用IUserIdProvider将SignalR用户映射到连接的示例

假设我们的应用程序使用userId来标识每个用户。现在,我们需要向特定用户发送消息。我们有userIdmessage,但SignalR还必须知道我们的userId和连接之间的映射关系。

为了实现这一点,首先需要创建一个新类,该类实现IUserIdProvider

public class CustomUserIdProvider : IUserIdProvider
{
     public string GetUserId(IRequest request)
    {
        // your logic to fetch a user identifier goes here.

        // for example:

        var userId = MyCustomUserClass.FindUserId(request.User.Identity.Name);
        return userId.ToString();
    }
}

第二步是告诉SignalR使用我们的CustomUserIdProvider而不是默认实现。这可以在Startup.cs中初始化hub配置时完成:

第二步是告诉 SignalR 使用我们的 CustomUserIdProvider 而非默认的实现。在启动时可以在 Startup.cs 中进行 Hub 配置的初始化:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var idProvider = new CustomUserIdProvider();

        GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);          

        // Any connection or hub wire up and configuration should go here
        app.MapSignalR();
    }
}

现在,您可以按照文档中提到的方式,使用特定用户的userId发送消息,例如:

public class MyHub : Hub
{
   public void Send(string userId, string message)
   {
      Clients.User(userId).send(message);
   }
}

9
MyCustomUserClass是什么? - Danny Bullis
4
"MyCustomUserClass"可以是您自定义的用户类,其中包含FindUserId方法。这只是一个例子。您可以在任何类中使用任何返回用户ID的方法,并在此处使用它。 - Sumant
6
谢谢@Sumant,我的问题最终是因为我在一个Web API项目中实现了OAuth 2和Bearer Token,所以我必须实现逻辑来通过查询字符串传递Bearer Token,因为它不能从那个初始的Signalr连接请求的头部中提取。不能仅使用request.User.Identity.Name。 - xinunix
1
@xinunix 在Web API项目中,您仍然可以使用HttpContext.Current.User.Identity.Name。您试过了吗? - Sumant
1
@Sumant 我已经解决了。问题是我在身份验证之前将 app.MapSignalR(); 放在了 global.asax 中。 - Mr. Robot
显示剩余16条评论

49

以下是一个开始.. 欢迎建议和改进。

服务器

public class ChatHub : Hub
{
    public void SendChatMessage(string who, string message)
    {
        string name = Context.User.Identity.Name;
        Clients.Group(name).addChatMessage(name, message);
        Clients.Group("2@2.com").addChatMessage(name, message);
    }

    public override Task OnConnected()
    {
        string name = Context.User.Identity.Name;
        Groups.Add(Context.ConnectionId, name);

        return base.OnConnected();
    }
}

JavaScript

(请注意上面服务器代码中的addChatMessagesendChatMessage也是方法)

    $(function () {
    // Declare a proxy to reference the hub.
    var chat = $.connection.chatHub;
    // Create a function that the hub can call to broadcast messages.
    chat.client.addChatMessage = function (who, message) {
        // Html encode display name and message.
        var encodedName = $('<div />').text(who).html();
        var encodedMsg = $('<div />').text(message).html();
        // Add the message to the page.
        $('#chat').append('<li><strong>' + encodedName
            + '</strong>:&nbsp;&nbsp;' + encodedMsg + '</li>');
    };

    // Start the connection.
    $.connection.hub.start().done(function () {
        $('#sendmessage').click(function () {
            // Call the Send method on the hub.
            chat.server.sendChatMessage($('#displayname').val(), $('#message').val());
            // Clear text box and reset focus for next comment.
            $('#message').val('').focus();
        });
    });
});

测试 在这里输入图片描述


7
@lgao 我不知道。 - The Muffin Man
@Thomas 我可能是为了演示而包含它的。必须有另一种方法来广播到特定的群体,因为这是硬编码的。 - The Muffin Man
这个简单的解决方案解决了我发送消息给特定已登录用户的问题。它简单、快速且易于理解。如果可以的话,我会多次点赞这个答案。 - Rafael A. M. S.
你是如何向特定用户发送消息的,因为代码中没有使用who变量.....你能帮我理解吗?谢谢。 - Mou
@Mou Clients.Group("2@2.com").addChatMessage(name, message); 或者查看上面的代码以动态方式实现。 - The Muffin Man
显示剩余7条评论

8
这是使用SignalR针对特定用户(不使用任何提供程序)的方法:
 private static ConcurrentDictionary<string, string> clients = new ConcurrentDictionary<string, string>();

 public string Login(string username)
 {
     clients.TryAdd(Context.ConnectionId, username);            
     return username;
 }

// The variable 'contextIdClient' is equal to Context.ConnectionId of the user, 
// once logged in. You have to store that 'id' inside a dictionaty for example.  
Clients.Client(contextIdClient).send("Hello!");

3
你是如何使用 contextIdClient 的?我没明白。 - Neo

5

如果有人尝试在asp.net core中实现此操作,您可以使用声明。

public class CustomEmailProvider : IUserIdProvider
{
    public virtual string GetUserId(HubConnectionContext connection)
    {
        return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
    }
}

任何标识符都可以使用,但必须是唯一的。例如,如果您使用名称标识符,则意味着如果有多个用户名与收件人相同,则消息也会被传递给他们。我选择电子邮件,因为它对每个用户都是唯一的。
然后在启动类中注册服务。
services.AddSingleton<IUserIdProvider, CustomEmailProvider>();

下一步。在用户注册期间添加声明。
var result = await _userManager.CreateAsync(user, Model.Password);
if (result.Succeeded)
{
    await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Model.Email));
}

发送消息给特定用户。
public class ChatHub : Hub
{
    public async Task SendMessage(string receiver, string message)
    {
        await Clients.User(receiver).SendAsync("ReceiveMessage", message);
    }
}

注意:消息发送者将不会被通知消息已发送。如果你想在发送者那端获得一个通知,请将SendMessage方法更改为以下内容。
public async Task SendMessage(string sender, string receiver, string message)
{
    await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", message);
}

只有在需要更改默认标识符时才需要执行这些步骤。否则,跳过到最后一步,通过传递userIds或connectionIds到SendMessage即可简单地发送消息。了解更多


1
是的,它有效!截至2022年,这是迄今为止最好的解决方案。使用非内置的ASP.NET Core Identity授权的唯一问题是SignalR用于通过Clients.User(userId)发送到特定用户的UserId不存在。我们只需要将其分配给任何现有唯一声明的用户,就像在此答案的第一个代码片段中一样。 - Niksr
这是 ASP.NET Core 的唯一可行(且功能正常)答案,谢谢! - Marco Coppola

2

请查看SignalR测试以了解该功能。

测试“SendToUser”会自动使用常规OWIN身份验证库传递的用户身份标识。

场景是您有一个用户从多个设备/浏览器连接,并且您想向他的所有活动连接推送消息。


谢谢你!但是项目版本2.0在这里无法编译SignalR。:(不幸的是我无法访问它。 - Igor

2

虽然是旧帖子,但我在一个示例中找到了这个:

services.AddSignalR()
            .AddAzureSignalR(options =>
        {
            options.ClaimsProvider = context => new[]
            {
                new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"])
            };
        });

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