【问题标题】:How to send messages to specific user using Azure SignalR in .NET Core如何在 .NET Core 中使用 Azure SignalR 向特定用户发送消息
【发布时间】:2021-08-24 04:30:38
【问题描述】:

.NET Core API 应用程序,我想使用它来将实时消息推送到 SPA。我有 azure 函数的工作示例,但现在我想将其转换为 Web API。

正在运行的 Azure 函数如下所示:

[FunctionName("Push")]
public static Task PushInfoSuccess([HttpTrigger(AuthorizationLevel.Anonymous, "post")] ILogger log, Models models
           [SignalR(HubName = "Hub1")] IAsyncCollector<SignalRMessage> signalRMessages)
{
     return signalRMessages.AddAsync(
               new SignalRMessage
               {
                   UserId = models.UserId,
                   Target = "Hub1",
                   Arguments = new[] { models}
               });
 }

我想使用 .NET Core API 重写。

我创建了如下所示的中心类

public class ChatHub : Hub
{
    public Task BroadcastMessage(string name, string message) =>
        Clients.All.SendAsync("broadcastMessage", name, message);

    public void Send(UserModel userModel)
    {
        Clients.User(userModel.UserId).SendAsync(userModel.Message);
    }

    public Task Echo(string name, string message) =>
        Clients.Client(Context.ConnectionId)
               .SendAsync("echo", name, $"{message} (echo from server)");
}

我有这个模型类:

public class UserModel
{
    public  string UserId { get; set; }
    public string Message { get; set; }
}

现在我有一些其他应用程序将通过 API 调用我的应用程序,因此我将添加控制器

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    [HttpGet]
    public ActionResult Get(UserModel userModel)
    {
        return Ok();
    }
}

我的其他应用程序将调用此 API 将通知推送到 SPA。推送通知时,我想将其推送到特定的用户 ID,我将通过 API 获取用户 ID 和消息。现在我想将消息推送到 UserModel.UserID。向特定用户发送消息时,我是否还需要考虑连接 ID?如果我有多个集线器,那么每个集线器是否会获得不同的连接 ID?在我的 SPA 应用程序中,我有多个集线器。那么连接ID和用户ID之间的关系是什么?有人可以在这里帮助我理解和帮助我吗?谢谢

【问题讨论】:

  • 是的,您还必须考虑ConnectionId。通常,我们应该有一个connectionIduserId 来广播我们的更新。因此,当您有多个集线器时,必须在 _userConnectionManager.GetUserConnections(model.userId); 上循环并将其发送到特定的连接,例如 await _notificationUserHubContext.Clients.Client(connectionId).SendAsync("sendToUser", yourModel); 但如果您有单个集线器,则只需将其推送到单个 connectId,在这种情况下不需要循环。
  • 感谢 MD Farid Uddin Kiron。所以会有一个集线器的connectionid。每当我有多个集线器时,我都会有多个连接ID。那么connectionids加上userid会变成uniquness吧?谢谢
  • 能否请您看一下示例,它可能会指导您。
  • @MrPerfect 使用并发字典来管理您的用户和连接。它们是线程安全的,但我还是建议使用 SemaphoreSlim 类。在您的字典中返回一个复杂对象,该对象可以为每个用户处理多个连接 ID。如果将这些连接 ID 存储在 HashSet 中,则不必担心重复。 SignalR 通常会在释放旧的连接 ID 之前分配一个新的连接 ID。

标签: c# asp.net-core signalr azure-signalr


【解决方案1】:

Connectionids 加上 userid 会变成唯一性吧?

是的,没错,应该如下所示:

HubController:

public class HubController : Controller
    {
       
        private readonly IHubContext<NotificationUserHub> _notificationUserHubContext;
        private readonly IUserConnectionManager _userConnectionManager;

        public HubController(IHubContext<NotificationHub> notificationHubContext, IHubContext<NotificationUserHub> notificationUserHubContext, IUserConnectionManager userConnectionManager)
        {
          
            _notificationUserHubContext = notificationUserHubContext;
            _userConnectionManager = userConnectionManager;
        }
        

        [HttpPost]
        public async Task<ActionResult> SendToSpecificUser(HubModel model)
        {
            var connections = _userConnectionManager.GetUserConnections(model.userId);
            if (connections != null && connections.Count > 0)
            {
                foreach (var connectionId in connections)
                {
                    await _notificationUserHubContext.Clients.Client(connectionId).SendAsync("sendToUser", model.Title, model.Message);
                }
            }
            return View();
        }
    }
}

通知用户中心:

public class NotificationUserHub : Hub
    {
        private readonly IUserConnectionManager _userConnectionManager;
        public NotificationUserHub(IUserConnectionManager userConnectionManager)
        {
            _userConnectionManager = userConnectionManager;
        }
        public string GetConnectionId()
        {
            var httpContext = this.Context.GetHttpContext();
            var userId = httpContext.Request.Query["userId"];
            _userConnectionManager.KeepUserConnection(userId, Context.ConnectionId);

            return Context.ConnectionId;
        }

        //Called when a connection with the hub is terminated.
        public async override Task OnDisconnectedAsync(Exception exception)
        {
            //get the connectionId
            var connectionId = Context.ConnectionId;
            _userConnectionManager.RemoveUserConnection(connectionId);
            var value = await Task.FromResult(0);//adding dump code to follow the template of Hub > OnDisconnectedAsync
        }
    }

用户连接管理器:

public class UserConnectionManager : IUserConnectionManager
    {
        private static Dictionary<string, List<string>> userConnectionMap = new Dictionary<string, List<string>>();
        private static string userConnectionMapLocker = string.Empty;

        public void KeepUserConnection(string userId, string connectionId)
        {
            lock (userConnectionMapLocker)
            {
                if (!userConnectionMap.ContainsKey(userId))
                {
                    userConnectionMap[userId] = new List<string>();
                }
                userConnectionMap[userId].Add(connectionId);
            }
        }

        public void RemoveUserConnection(string connectionId)
        {
            //Remove the connectionId of user 
            lock (userConnectionMapLocker)
            {
                foreach (var userId in userConnectionMap.Keys)
                {
                    if (userConnectionMap.ContainsKey(userId))
                    {
                        if (userConnectionMap[userId].Contains(connectionId))
                        {
                            userConnectionMap[userId].Remove(connectionId);
                            break;
                        }
                    }
                }
            }
        }
        public List<string> GetUserConnections(string userId)
        {
            var conn = new List<string>();
            lock (userConnectionMapLocker)
            {
                conn = userConnectionMap[userId];
            }
            return conn;
        }
    }

型号:

public class HubModel 
    {
        public string Title { get; set; }
        public string Message { get; set; }
        public string userId { get; set; }
    }

希望对你有所帮助。

【讨论】:

  • 您好,感谢您的回答。我对信号器概念很陌生,我只是想知道 _userConnectionManager 和 _notificationUserHubContext 是从哪里来的。您对此有任何实施吗?谢谢。我们还需要将这些连接 ID 存储在任何地方吗?
  • 是的,我有很长的实现。在这里,我连接了集线器控制器。无需为实现方面添加 coonectionIds
  • 嗨,Md farid Uddin Kiron,您可以分享一些有关 _notificationUserHubContext 和 _userConnectionManager 的详细信息。我正在尝试实施,但现在坚持了下来。谢谢
  • 好的,我正在尝试更新答案,离工作文件有点远。
  • 非常感谢 Md farid Uddin Kiron
【解决方案2】:

我的设置是这样的

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
   app.UseSignalR(routes =>
        {
            routes.MapHub<ChatHub>("/chat");
        });
}




public void ConfigureServices(IServiceCollection services)
        {
 services.AddAuthentication()
              .AddJwtBearer(cfg =>
              {
                  cfg.TokenValidationParameters = new TokenValidationParameters()
                  {
                      ValidIssuer = configuration["Tokens:Issuer"],
                      ValidAudience = configuration["Tokens:Audience"],
                      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Tokens:Key"]))
                  };

                  cfg.Events = new JwtBearerEvents
                  {
                      OnMessageReceived = context =>
                      {
                          var accessToken = context.Request.Query["access_token"];

                          // If the request is for our hub...
                          var path = context.HttpContext.Request.Path;
                          if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/chat")))
                          {
                              // Read the token out of the query string
                              context.Token = accessToken;
                          }
                          return Task.CompletedTask;
                      }
                  };
              });
}

以及像下面这样继承 Hub 的类

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
    public class ChatHub : Hub
    {
        

        public async Task SendMessage(MessageModel msg)
        {   
            if (!string.IsNullOrEmpty(msg.ClientUniqueId))
            {
                await Clients.Client(msg.ClientUniqueId).SendAsync("ReceiveMessage", chat);

            }
        }
      
    }

【讨论】:

    猜你喜欢
    • 2015-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多