使用 SignalR 集线器向特定连接用户广播消息
Posted
技术标签:
【中文标题】使用 SignalR 集线器向特定连接用户广播消息【英文标题】:Broadcast messages to specific connected users using SignalR hub 【发布时间】:2014-06-10 08:07:53 【问题描述】:我的目标是当数据库发生插入/更新时立即通知连接的用户。
我有一个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 供我的 Entity Framework MemberShip 用户广播消息,因为每次客户端离开时都会调用 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
引用,如果客户端调用服务器方法,我使用它来获取一些数据。
此外,所有Controllers
都在使用IWorker
具体实现,并且具有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();
在我的工人阶级的某个地方:
List<NotificationMessage> testList = new List<NotificationMessage>
new NotificationMessage Message = "TEST MESSAGE!"
;
Clients.Group("TestGroup").updateNotificationMessages(testList);
我想我错过了什么!!!
【问题讨论】:
【参考方案1】:我的 2 美分:你看过Clients.User()
方法吗?它已经为您完成了所有ConnectionId
映射,并且您正在使用Context.User
,因此它应该可以开箱即用。如果您需要更复杂的东西,您可以在IUserIdProvider
的自定义实现中编写自己的映射逻辑。至少您会消除一个级别的复杂性(但如果问题不是因为这个问题,则不确定它是否会解决您的特定问题)。只是一个想法。
【讨论】:
是的,我会将逻辑移至 IUserIdProvider,因为我使用了 ASP.NET 身份验证,这似乎更好。我将尝试 Clients.User() 空重载。 Clients.User() 对我的问题没有意义,因为我可能有多个连接的用户需要接收相同的消息。 嗯,我看不出这与您使用 ConnectionId 的方法有什么不同。不是循环访问“不安全”的连接 ID,而是循环访问用户 ID,这是一个不太需要解决的问题。另外,我建议您看看是否可以找到一种方法以某种方式对用户进行分组(例如使用角色)并利用 SignalR 的 Groups 支持,这样您只需一个电话即可同时击中所有用户。也许你可以在那个方向回顾你的逻辑。 是的,我同意您的评论,我将删除当前逻辑并构建新的逻辑,尝试使用来自 Hub 的 Groups 和 ConnectionId。有没有办法让所有当前连接到集线器客户端 ConnectionIds?我了解 IUserIdProvider 将让我将 ConnectionId 与我的数据库中的 User.Identity id 映射。当然,对于群组,我只需要说向群组发送消息,理论上所有连接到群组的客户端都会收到通知! AFAIK 没有办法获取所有当前的 ConnectionId,但是,即使这可能不是推荐的方法。您对 ConnectionId 的干扰越少越好,而且通常可以在不使用它们的情况下重组代码以解决问题。小组真的很棒。以上是关于使用 SignalR 集线器向特定连接用户广播消息的主要内容,如果未能解决你的问题,请参考以下文章
如何在 hubpipleline 之外使用 SignalR 集线器实例