使用SignalR集线器向特定连接的用户广播消息

本文关键字:用户 广播 消息 连接 SignalR 集线器 使用 | 更新日期: 2023-09-27 18:07:48

我的目标是当数据库发生插入/更新时,立即通知连接的用户。

我有一个Hub

[Authorize]
public class NotificationsHub : Hub
{
    private readonly IWorker _worker;
    public NotificationsHub() : this(new WorkerFacade(new ExtremeDataRepository())) { }
    public NotificationsHub(IWorker worker)
    {
        _worker = worker;
    }
    public override Task OnConnected()
    {
        if (!HubUsersManager.ConnectedUsers.Any(p => p.Name.Equals(Context.User.Identity.Name)))
        {
            HubUsersManager.ConnectedUsers.Add(new HubUserHandler
            {
                Name = Context.User.Identity.Name,
                ConnectionId = Context.ConnectionId
            });
        }
        return base.OnConnected();
    }
    //public override Task OnDisconnected()
    //{
    //    HubUserHandler hubUserHandler =
    //        HubUsersManager.ConnectedUsers.FirstOrDefault(p => p.Name == Context.User.Identity.Name);
    //    if (hubUserHandler != null)
    //        HubUsersManager.ConnectedUsers.Remove(hubUserHandler);
    //    return base.OnDisconnected();
    //}
    public async Task<ICollection<NotificationMessage>> GetAllPendingNotifications(string userId)
    {
        return await _worker.GetAllPendingNotifications(userId);
    }
    public void UpdateNotificationMessagesToAllClients(ICollection<NotificationMessage> notificationMessages)
    {
        Clients.All.updateNotificationMessages(notificationMessages);
    }
}

正如你所看到的,我从连接用户的手动映射中删除了OnDisconnected,以使用户名和ConnectionId稍后用于我的实体框架会员用户广播消息,因为每次客户端离开集线器而不是实际断开连接时都会调用OnDisconnected,例如关闭浏览器。

SignalR的初始化工作完美,我测试了它,GetAllPendingNotifications在连接开始时被调用,我调用SignalR Javascript代码的函数,见下面的代码。

// A simple templating method for replacing placeholders enclosed in curly braces.
if (!String.prototype.supplant) {
    String.prototype.supplant = function (o) {
        return this.replace(/{([^{}]*)}/g,
            function (a, b) {
                var r = o[b];
                return typeof r === 'string' || typeof r === 'number' ? r : a;
            }
        );
    };
}
if (typeof String.prototype.endsWith !== 'function') {
    String.prototype.endsWith = function(suffix) {
        return this.indexOf(suffix, this.length - suffix.length) !== -1;
    };
}
$(function () {
    // Reference the auto-generated proxy for the hub.
    var notificationsHub = $.connection.notificationsHub;
    var userName, userId;
    // Create a function that the hub can call back to display messages.
    notificationsHub.client.updateNotificationMessages = updateNotificationsToPage;
    function updateNotificationsToPage(notificationMessages) {
        alert('Im in updating notifications');
        if (notificationMessages.count > 0) {
            $.each(function(index) {
                alert($(this).Message);
            });
        }
    };
    function init() {
        notificationsHub.server.getAllPendingNotifications(userId).done(updateNotificationsToPage);
    }
    function userDetailsRetrieved(data) {
        userName = data.UserName;
        userId = data.Id;
        // Start the connection.
        $.connection.hub.start().done(init);
    }
    function getUserDetails() {
        var baseURL = document.baseURI,
            url;
        if (!baseURL.endsWith('Home/GetUserDetails')) {
            if (baseURL.endsWith('/'))
                url = baseURL + 'Home/GetUserDetails';
            else if (baseURL.endsWith('/Home') ||
                baseURL.endsWith('/Home/'))
                url = baseURL + '/GetUserDetails';
            else {
                url = baseURL + '/Home/GetUserDetails';
            }
        } else
            url = baseURL;
        $.ajax({
            url: url, success: userDetailsRetrieved, type: 'POST', error: function () {
            console.log(arguments);
        }, dataType: 'json' });
    }
    getUserDetails();
});

我知道映射可能不是必要的,我可以覆盖实现将其映射到我实际的ApplicationUser GUID Id以避免它,但现在它似乎服务于目的,如果我首先使它工作,可能会改变它。

您还可以看到IWorker引用,如果客户机调用服务器方法,我使用它来获取一些数据。此外,IWorker的具体实现正在被所有Controllers使用,并且具有Clients属性,因此我可以立即将消息广播回连接的客户端。

private IHubConnectionContext _clients;
private IHubConnectionContext Clients
{
    get {
        return _clients ?? (_clients = GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>().Clients);
    }
}

已正确填充。在应用程序的某个点,IWorker调用Clients属性。

foreach (HubUserHandler hubUserHandler in HubUsersManager.ConnectedUsers)
        {
            ApplicationUser user = await GetApplicationUserWithName(hubUserHandler.Name);
            List<string> userRoles = await GetUserRoles(user);
            bool isInRole = userRoles.Any(p => p.Equals("ARole"));
            List<NotificationMessage> testList = new List<NotificationMessage>
            {
                new NotificationMessage {Message = "TEST MESSAGE!"}
            };
            if (isInRole)
                Clients.Client(hubUserHandler.ConnectionId).updateNotificationMessages(testList);
        }

我的浏览器什么都没有,应该弹出两个警报,一个说Im in updating notifications,一个说TEST MESSAGE,就像你在上面的Javascript代码中看到的那样。

[编辑]

更改代码发送消息到特定的Group,但没有再次显示到我的客户端浏览器。

        public override Task OnConnected()
        {
            if (_groupManager.Count == 0)
            {
                ICollection<string> rolesCollection = _worker.GetAllRoles();
                foreach (string roleName in rolesCollection)
                {
                    _groupManager.Add(roleName);
                }
            }
            foreach (string groupRole in _groupManager.Groups)
            {
                if (Context.User.IsInRole(groupRole))
                {
                    Groups.Add(Context.ConnectionId, groupRole);
                }
            }
            return base.OnConnected();
        }

在我的worker类的某个地方:

    List<NotificationMessage> testList = new List<NotificationMessage>
        {
            new NotificationMessage {Message = "TEST MESSAGE!"}
        };
    Clients.Group("TestGroup").updateNotificationMessages(testList);

我想我错过了什么!

使用SignalR集线器向特定连接的用户广播消息

我的两分钱:你看过Clients.User()方法吗?它已经为您完成了所有ConnectionId映射,并且您正在使用Context.User,因此它应该开箱即用。如果您需要更复杂的东西,您可以在IUserIdProvider的自定义实现中编写自己的映射逻辑。至少你可以消除一个层次的复杂性(但如果问题不是因为这个,就不确定它是否能解决你的具体问题)。这只是一个想法。