SignalR - sending push notification to a specific user - signalr

I'm working on a PoC for a notification engine. I'm able to successfully connect and call Hub functions from JS, but I can't seem to get push notifications to work. I'm getting an Object reference not set to an instance of an object error.
Triggering class
// I was able to confirm that the connectionIds are valid
public void HandleEvent(NewNotificationEvent eventMessage)
{
// _connections handles connectionids of a user
// multiple connection ids to handle multiple open tabs
var connectionIds = _connections.GetConnectionsByUser(eventMessage.Notification.UserId);
foreach(var connectionId in connectionIds)
{
// a client is returned, but aside from the connectionid, all the properties are either null or empty
var client = _notificationHub.Clients.Client(connectionId);
///// ERROR HAPPENS HERE
///// I'm guessing NewNotification() isn't defined somewhere, but I don't know where.
client.NewNotification("Hello");
}
}
View.cshtml
var notificationHub = $.connection.NotificationHub;
$.connection.hub.qs="userId=#userId"
// Initialization
$.connection.hub.start().done(function () {
// get count unread notification count
notificationHub.invoke("unReadNotificationsCount")
.done((unreadCount) => {
if (unreadCount > 0) {
$('#notificationBadge').html(unreadCount);
hasNewNotification = true;
}
console.log('SignalR connected!');
})
.fail((data) => {
console.log('Unable to reach server');
console.log(data);
})
.always(() => $('#dropdownNotificationOptions').show());
});
// also tried notificationHub.NewNotification()
notificationHub.client.NewNotification = function (notification) {
console.log(notification);
}
NotificationHub.cs
[HubName("NotificationHub")]
public class NotificationHub : Hub
{
//ctor
public override Task OnConnected()
{
var userId = Context.QueryString["userid"];
if(userId.IsNotNullOrEmpty())
_connections.Add(Context.ConnectionId, Guid.Parse(userId));
else
_connections.Add(Context.ConnectionId, Guid.NewGuid());
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled = true)
{
_connections.Remove(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
Guid userId;
if (Guid.TryParse(Context.QueryString["userid"],out userId))
{
//var userId = _workContext.CurrentUser.Id;
var userConnection = _connections.GetUserByConnection(Context.ConnectionId);
if (userConnection == null || userConnection.IsNotNullOrEmpty())
{
_connections.Add(Context.ConnectionId, userId);
}
}
return base.OnReconnected();
}
}

You should have your NewNotification before the $.connection.hub.start() such as:
var notificationHub = $.connection.NotificationHub;
$.connection.hub.qs="userId=#userId"
// Moved to define before the connection start
notificationHub.client.NewNotification = function (notification) {
console.log(notification);
}
// Initialization
$.connection.hub.start().done(function () {
// get count unread notification count
notificationHub.invoke("unReadNotificationsCount")
.done((unreadCount) => {
if (unreadCount > 0) {
$('#notificationBadge').html(unreadCount);
hasNewNotification = true;
}
console.log('SignalR connected!');
})
.fail((data) => {
console.log('Unable to reach server');
console.log(data);
})
.always(() => $('#dropdownNotificationOptions').show());
});

Related

MassTransit handle Publish exception

I'm using an IHostedService in order to Publish a list of events to RabbitMQ, using MassTransit.
I now wanted to handle exceptions on Publish, when for example RabbitMQ is not available.
Then my idea is to mark the db row relative to the message to be sent with Error = 1, TransientError = 1, so that next time it will be tried to be sent again.
Here is how I configure MassTransit
services.AddMassTransit(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(_configuration["RabbitMQ:URI"] + _configuration["RabbitMQ:VirtualHost"]), $"ENG {_configuration["Application:PlantID"]} Producer", h =>
{
h.Username(_configuration["RabbitMQ:UserName"]);
h.Password(_configuration["RabbitMQ:Password"]);
});
cfg.Publish<UpdateNorm>(x =>
{
x.Durable = true;
x.AutoDelete = false;
x.ExchangeType = "fanout"; // default, allows any valid exchange type
});
cfg.ConfigurePublish(x => x.UseExecute(x =>
{
x.Headers.Set("SiteID", _configuration["Application:PlantID"]);
}));
}));
});
//OPTIONAL, but can be used to configure the bus options
services.AddOptions<MassTransitHostOptions>()
.Configure(options =>
{
// if specified, waits until the bus is started before
// returning from IHostedService.StartAsync
// default is false
options.WaitUntilStarted = false;
// if specified, limits the wait time when starting the bus
//options.StartTimeout = TimeSpan.FromSeconds(10);
// if specified, limits the wait time when stopping the bus
options.StopTimeout = TimeSpan.FromSeconds(30);
});
And here my IHostedService implementation
public class MessageBrokerQueueBackgroundService : BackgroundService
{
private readonly ILogger<MessageBrokerQueueBackgroundService> logger;
private readonly IPublishEndpoint publishEndpoint;
private readonly int MessageBrokerQueueCheckMillis;
private readonly DB db;
private readonly BLMessageBrokerQueue blMessageBrokerQueue;
public MessageBrokerQueueBackgroundService(
DB db,
BLMessageBrokerQueue blMessageBrokerQueue,
IPublishEndpoint publishEndpoint,
ILogger<MessageBrokerQueueBackgroundService> logger,
IConfiguration configuration)
{
this.db = db;
this.blMessageBrokerQueue = blMessageBrokerQueue;
this.publishEndpoint = publishEndpoint;
this.logger = logger;
this.MessageBrokerQueueCheckMillis = Convert.ToInt32(configuration["MessageBrokerQueueCheckMillis"]);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogDebug($"MessageBrokerQueueBackgroundService is starting.");
stoppingToken.Register(() =>
{
End(stoppingToken);
});
while (!stoppingToken.IsCancellationRequested)
{
ICollection<MessageBrokerQueue> messageBrokerqueueList;
try
{
messageBrokerqueueList = await blMessageBrokerQueue.GetMessageBrokerQueueListAsync();
foreach (var element in messageBrokerqueueList.OrderBy(x => x.PK))
{
try
{
if (element.Context == "Norm")
{
await publishEndpoint.Publish<UpdateNorm>(new
{
element.Key1,
}, stoppingToken);
}
// define other contexts
else
{
throw new MessageBrokerQueueUnknownContextException($"Unknown Context: {element.Context}", element.Context);
}
await blMessageBrokerQueue.MessageSentAsync(element);
logger.LogInformation("MessageBrokerQueueBackgroundService Message Context: {Context}, Key1: {Key1}, Key2: {Key2}, Key3: {Key3} correctly Published.", element.Context, element.Key1, element.Key2, element.Key3);
}
catch (MessageBrokerQueueUnknownContextException e)
{
logger.LogError(e, "MessageBrokerQueueBackgroundService unknown Context: {Context}.", e.Context);
await blMessageBrokerQueue.MessageNonTransientErrorAsync(element, $"Unknown Context {e.Context}");
}
//catch (Exception Rabbit not available e)
//{
// logger.LogError(e, "MessageBrokerQueueBackgroundService Generic Exception threaded as transient");
// await blMessageBrokerQueue.MessageTransientErrorAsync(element, e.Message);
//}
catch (Exception e)
{
logger.LogError(e, "MessageBrokerQueueBackgroundService Generic Exception threaded as NOT transient");
await blMessageBrokerQueue.MessageNonTransientErrorAsync(element, e.Message);
}
}
}
catch (Exception e)
{
logger.LogError(e, $"MessageBrokerQueueBackgroundService error while processing queue.");
}
finally
{
await Task.Delay(MessageBrokerQueueCheckMillis, stoppingToken);
}
}
}
protected Task End(CancellationToken stoppingToken)
{
logger.LogDebug($"MessageBrokerQueueBackgroundService background task is stopping.");
return Task.CompletedTask;
}
}
I tried to Publish with Rabbit shut down, but this method hang forever
await publishEndpoint.Publish<UpdateNorm>(new
{
element.Key1,
}, stoppingToken);
until I restart Rabbit, then it continues and finishes correctly.
I want to avoid to wait indefinitely. My idea was to wait for some seconds and then mark for this exception the error as transient.

How can I easily check whether the user submitting a query belongs to them or not in .net core?

Authorization Set
services.AddAuthorization(options =>
{
options.AddPolicy("MustNutritionist", policy =>
policy.RequireClaim("nutritionistId"));
});
Controller
NutritionistUpdateModel have id field.
[Authorize(Policy = "MustNutritionist")]
public BaseResponseModel PostEdit([FromForm] NutritionistUpdateModel nutritionistUpdateModel)
{
try
{
var result = nutritionistService.EditNutritionist(nutritionistUpdateModel);
if (result)
{
return new SuccessResponseModel<bool>(result);
}
else
{
return new BaseResponseModel(ReadOnlyValues.NutritionistNotFound);
}
}
catch (Exception ex)
{
return new BaseResponseModel(ex.Message);
}
}
Token Generation Claim
claims.Add(new Claim("nutritionistId", nutritionistId.ToString()));
Problem
I want to check equation of NutritionistUpdateModel.Id and Claims.nutritionistId. I can check with below code.But i must write lots of if else statement.Is there any easy way ?
private bool ChechNutritionistAuthorize(int nutritionistId)
{
var currentUser = HttpContext.User;
var nutritionistIdClaim=Int32.Parse(currentUser.Claims.FirstOrDefault(c => c.Type == "NutritionistId").Value);
if (nutritionistIdClaim == nutritionistId)
{
return true;
}
else
{
return false;
}
}
Using extension method like this
public static class IdentityExtensions
{
public static bool ValidateNutritionistId(this ClaimsPrincipal principal, int nutritionistId)
{
if (principal == null)
throw new ArgumentNullException(nameof(principal));
int.TryParse(principal.Claims.FirstOrDefault(c => c.Type == "NutritionistId").Value, out int nutritionistIdClaim);
return nutritionistIdClaim == nutritionistId;
}
}
and you can use like this
HttpContext.User.ValidateNutritionistId(your id here )
and you also need to add using statement and reuse same method in all of your Controllers

SignalR in the loop

I am using SignalR in loop like this:
int itemsCount = 100;
for (int i = 0; i <= itemsCount; i++)
{
Thread.Sleep(100);
SummaryHub.SendMessages("test", i.ToString());
}
and client site is:
$(function () {
var notifications = $.connection.SummaryHub;
// Create a function that the hub can call to broadcast messages.
notifications.client.broadcastMessage = function (name, message) {
console.log(name);
console.log(message);
};
// Start the connection.
$.connection.hub.start().done(function () {
// $.connection.hub.start({ waitForPageLoad: false }).done(function () {
console.log("connection started")
}).fail(function (e) {
alert(e);
});
});
[HubName("SummaryHub")]
public class SummaryHub : Hub
{
[HubMethodName("sendMessages")]
public static void SendMessages(string name, string message)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<SummaryHub>();
context.Clients.All.broadcastMessage(name, message);
}
}
Problem is that client receive messages after loop is done, not during the loop. How can I fix that? Thanks
I find out. It was ajax call with async:false and that was preventing sending messages to the client. Change async to true solved the problem

SignalR 2.0 Hub public method not accecible via Java Script

I'm trying to get list of connected users by using the following server code in SignalR hub. For store in-memory data I'm using following class:
public class UserInfo
{
public string ConnectionId { get; set; }
public string UserName { get; set; }
public string Role { get; set; }
}
When user connected I'm adding user to the list of connected users:
public override Task OnConnected()
{
if (Context.User.Identity.IsAuthenticated)
{
if (Context.User.IsInRole("User"))
{
ui.Add(new UserInfo { ConnectionId = Context.ConnectionId, UserName = Context.User.Identity.Name, Role = "User" });
}
else
{
ui.Add(new UserInfo { ConnectionId = Context.ConnectionId, UserName = Context.User.Identity.Name, Role = "Operator" });
}
}
return base.OnConnected();
}
Here is the way I'm getting list of currently connected users:
public IEnumerable<UserInfo> GetUsers()
{
var x = (from a in ui where a.Role == "User" select new UserInfo { UserName = a.UserName, ConnectionId = a.ConnectionId }).ToList();
return x;
}
public IEnumerable<UserInfo> GetOperators()
{
var y = (from a in ui where a.Role == "Operator" select new UserInfo { UserName = a.UserName, ConnectionId = a.ConnectionId }).ToList();
return y;
}
Unfortinately public method GetOperators/GetUsers not accessible and I did not receive data on client side:
$(function () {
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
//Here I'm calling hub public methods
chat.getOperators = function (data) {
alert(data);
};
chat.getUsers = function (data) {
alert(data);
};
// Create a function that the hub can call to broadcast messages.
chat.client.addChatMessage = function (name, message) {
// Html encode display name and message.
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
// Add the message to the page.
$('#discussion').append('<li><strong>' + encodedName
+ '</strong>: ' + encodedMsg + '</li>');
};
// Get the user name and store it to prepend to messages.
$('#displayname').val(prompt('Enter your name:', ''));
// Set initial focus to message input box.
$('#message').focus();
// Start the connection.
$.connection.hub.start().done(function () {
$('#sendmessage').click(function () {
// Call the Send method on the hub.
chat.server.sendChatMessage($('#displayname').val(), $('#message').val());
// Clear text box and reset focus for next comment.
$('#message').val('').focus();
});
});
});
Your syntax for the calls to the server is wrong; this here:
chat.getUsers = function (data) {
alert(data);
};
will simply define chat.getUsers to be a function.
You probably want
chat.server.getUsers().done(function(data) {
console.log(data);
}).fail(function(error) {
console.log("failed to get data", error);
});
Take another look at the documentation.
please can you try this
//on your client action do a server call
chat.server.getOperators();
and
//on your client action do a server call
chat.server.getUsers();
instead of
chat.getOperators = function (data) {
alert(data);
};
chat.getUsers = function (data) {
alert(data);
};

Return count of connected clients in SignalR. Not firing client function

I am trying to return the count of all connections to a web client with SignalR. I increment and persist the client count by firing logic on the hub OnConnected() method.
public class PopHub : Hub
{
public static List<string> Users = new List<string>();
public override Task OnConnected()
{
var clientId = GetClientId();
if (Users.IndexOf(clientId) == -1)
{
Users.Add(clientId);
}
Send(Users.Count);
return base.OnConnected();
}
public void Send(int count)
{
Clients.All.updateUsersOnlineCount(count);
}
stepping through my code with an external console client (to trigger OnConnected()) shows that I am traversing through Send(int count) with a count of 1.
On my web client, I configure my JS as such
$(function() {
var hub = $.connection.popHub;
hub.client.updateUsersOnlineCount = function(count) {
console.log(count);
};
$.connection.hub.start().done(function() {
console.log('connected');
});
}());
And lastly my snippet from the generated js
proxies.popHub = this.createHubProxy('popHub');
proxies.popHub.client = { };
proxies.popHub.server = {
popClient: function (message) {
return proxies.popHub.invoke.apply(proxies.popHub, $.merge(["PopClient"], $.makeArray(arguments)));
},
query: function () {
return proxies.popHub.invoke.apply(proxies.popHub, $.merge(["Query"], $.makeArray(arguments)));
},
send: function (count) {
return proxies.popHub.invoke.apply(proxies.popHub, $.merge(["Send"], $.makeArray(arguments)));
}
};
**Note that Popclient and Query are unrelated server side events, of which do work giving me somewhat of a sanity check. Any idea why my clients updateUsersOnlineCount function is not logging the count of connections as I expect?
Instead of doing it in the OnConnected, please give this a try, it might be that the Base.OnConnected has not been executed yet, so it's not ready to broadcast to clients.
//Client
$.connection.hub.start().done(function() {
console.log('connected');
hub.server.ClientCount();
});
//Hub
public static List<string> Users = new List<string>();
public override Task OnConnected()
{
var clientId = GetClientId();
if (Users.IndexOf(clientId) == -1)
{
Users.Add(clientId);
}
//Send(Users.Count); //not calling this since it's not working
return base.OnConnected();
}
public void ClientCount()
{
Clients.All.updateUsersOnlineCount(Users.Count);
}

Resources