Reputation: 8626
My goal is when an insert/update happens to the database to immediately notify connected users.
I have a 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);
}
}
As you can see I removed the OnDisconnected from the manual mapping of connected users, to have the username and the ConnectionId to use later for my Entity Framework MemberShip users to broadcast messages, because the OnDisconnected is invoked everytime a client goes away of the hub and not when actually disconnects, e.g. close the browser.
The initialization of SignalR works perfect and I tested it, GetAllPendingNotifications
is called upon connection start and I invoke the function of the SignalR Javascript code, see below the code.
// 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();
});
I know that mapping might be not necessary, and I can override the implementation to map it to my actual ApplicationUser
GUID Id to avoid it, but for now it seems to serve the purpose, might change it if I first make it work.
You also can see the IWorker
reference that I use to get some data if a client invokes a server method.
Additionally the IWorker
concrete implementation is being used by all the Controllers
and has a property Clients
so I can broadcast messages back to connected clients immediately.
private IHubConnectionContext _clients;
private IHubConnectionContext Clients
{
get {
return _clients ?? (_clients = GlobalHost.ConnectionManager.GetHubContext<NotificationsHub>().Clients);
}
}
Which is populated properly.
And in some point of the application the IWorker
is invoking the Clients
property.
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);
}
Nothing comes to my browser, two alerts should popup, one saying Im in updating notifications
and one TEST MESSAGE
like you see in the Javascript
code above.
[EDIT]
Changed code to send message to specific Group
but nothing again shows up to my client browser.
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();
}
Somewhere in my worker class:
List<NotificationMessage> testList = new List<NotificationMessage>
{
new NotificationMessage {Message = "TEST MESSAGE!"}
};
Clients.Group("TestGroup").updateNotificationMessages(testList);
I guess I miss something!!!
Upvotes: 0
Views: 2332
Reputation: 3425
My 2 cents: did you have a look into the Clients.User()
method? It already does all the ConnectionId
mapping for you, and you are using Context.User
so it should work out of the box. If you need something more complex you can write you own mapping logic inside a custom implementation of IUserIdProvider
. At least you would remove one level of complexity (but not sure it would solve your specific issue if the problem is not because of that). Just an idea.
Upvotes: 2