如何使用 .NET Core 中的 Azure SignalR 向特定用户发送消息

How to send messages to specific user using Azure SignalR in .NET Core

.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。

我创建了 hub class 如下所示

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)");
}

我有这个型号class:

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。推送通知时,我想将它推送到特定的用户标识,我将通过 API 获取用户标识和消息。现在我想将消息推送到 UserModel.UserID。向特定用户发送消息时,是否还需要考虑连接 ID?如果我有多个集线器,那么我是否会为每个集线器获得不同的 connectionid?在我的 SPA 应用程序中,我有多个集线器。那么connection id和userid之间的关系是什么?有人可以在这里帮助我理解和帮助我吗?谢谢

Connectionids加上userid会变成uniquness吧?

是的,你是对的,应该如下所示:

集线器控制器:

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; }
    }

希望对您有所帮助。

我有这样的设置

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的class

[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);

            }
        }
      
    }