How to use SignalR to re-direct after Session timeout? - signalr

I am using SignalR to redirect my app after session timeout:
void Session_End(object sender, EventArgs e)
{
var HubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
HubContext.Clients.All.clientListener(false);}
And my Hub class:
public class MyHub : Hub
{
public void ServerListener(bool result)
{
Clients.Caller.ClientListener(result);
}
}
JavaScript:
(function () {
var myHub = $.connection.myHub;
$.connection.hub.start()
.done(function () {
console.log("Connected");
})
.fail(function () {
alert("Failed!");
})
myHub.client.clientListener = function (data) {
if (data == false)
window.location.href = "/Home/Index";//#Url.Action("Index","Home");
}
})();
The Problems that I face is:
When multiple users are connected all users are logged out at the same time, even if they logged in at different times.
Logout redirection occurs even if regular requests are made.
It would really help me, if someone could tell me how to do a "server push" without invoking the Session_End() as I want to use some other session state other than "In Proc".

Regarding Question #1:
Since you're using HubContext.Clients.All.clientListener all users connected to SignalR are indeed going to receive the message from the server - it doesn't matter when they logged in to your app.
You should use this guide to send a message from the server to a specific user: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections .
Personally I like single-user groups idea https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections#single-user-groups.
As for the additional question, as long as you can get the hub reference with GlobalHost.ConnectionManager.GetHubContext<MyHub>() all you have to do is call a registered client side method to perform a server push (e.g. Clients.Group(userid).clientListener(false)). It's not something that has to be done exclusively on Session_End().

What I did is:
On Page_Load I update a hidden label with the Session.SessionId
Pass it via query string before starting hub on client:
$.connection.hub.qs = { "sessionId": $("#lblSessionId").text() };
On the server side in the hub OnConnected/OnReconnected I call this method to store the correlation of sessionId->connectionId in dictionary:
private void AddConnectedClient()
{
// read the [ASP.Net] sessionId we send from client in queryString
string sessionId = Context.Request.QueryString["sessionId"];
if (!string.IsNullOrEmpty(sessionId))
sessionIdToConnectionId.AddOrUpdate(sessionId, Context.ConnectionId, (k, v) => Context.ConnectionId);
}
Now in the Session_End I have this code (GetConnectionIdFromSessionId is a static method I added in hub to read from sessionIdToConnectionId dictionary shown in #3):
protected void Session_End(object sender, EventArgs e)
{
// look for signalR connecitonId
string connectionID = YourHub.GetConnectionIdFromSessionId(Session.SessionID);
if (connectionID != null)
GlobalHost.ConnectionManager.GetHubContext<YourHub>().Clients.Client(connectionID).onSessionExpired();
}

Related

How to make a Blazor page update the content of one html tag with incoming data from gRPC service

So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/

Push Data To Client Using SignalR

I'm sorry for potentially creating a duplicate thread here, but I simply cannot get my web application to do what I need by following the other examples I've found.
My goal is to do one of the following:
OPTION 1 - IDEAL SOLUTION
Fetch data from a database and update the UI on a web page ONLY when changes are made to the data being displayed on the web page. For example, if a user is viewing a service ticket, I don't want to update the UI on that page unless that ticket is changed.
OPTION 2 - ACCEPTABLE SOLUTION
Fetch data from a database every x seconds and use that data to update the UI on a web page.
My current implementation of Option 2 is below. It involves sending an asynchronous HTTP request every 60 seconds to fetch the data:
// start checking for new messages every 60 seconds
setInterval(function () {
$.ajax({
async: true,
type: "POST",
contentType: "application/json; charset=utf-8;",
url: "/AJAX_Handlers/CheckForNewMessages.ashx",
dataType: "json",
success: function (Result) {
var new_message_received = Result[0]["NewMessageReceived"];
if (new_message_received) {
$("#DIVMessageReminder").html("<strong>You have " + num_new_messages + " new message(s).</strong>");
$("#DIVMessageReminder").show();
}
else {
$("#DIVMessageReminder").hide();
}
}
});
}, 60000);
Rather than sending an HTTP request every 60 seconds, I would like to use SignalR to push that data to the client every 60 seconds.
As a simple example, I have created the following Hub with a method to get the current time on the server:
Imports Microsoft.AspNet.SignalR
Public Class ServerTimeHub
Inherits Hub
Public Sub GetServerTime()
Dim current_time As String = Now.ToString()
Clients.All.updateTime(current_time)
End Sub
End Class
And a basic textbox:
<input id="TXTLongPollingTest" type="text" class="form-control" />
And my client-side code:
var hub = $.connection.serverTimeHub;
hub.client.updateTime = function (new_time) {
$("#TXTLongPollingTest").val(new_time);
}
$.connection.hub.start().done(function () {
alert("connected to the SignalR hub");
hub.getServerTime();
}).fail(function (err) {
alert("failed to connect to SignalR hub: " + err);
});
At first I tried getting it to fetch the server time just once. My code will successfully connect to the hub, but then it throws an error saying "Uncaught TypeError: hub.getServerTime is not a function". That's the first problem I haven't been able to overcome.
The second problem is: How can I get the hub to send the current time to the client on a regular interval such as every 1 second?
Here is what I have done to achieve something similar. Essentially fetching data from the database and broadcasting to clients every 30 seconds.
In my global.asax.cs I have this to ensure whenever my website is has started/restarted it will kick off my repeater:
protected void Application_Start(object sender, EventArgs e)
{
GetTeamData.TeamDataRepeater();
}
In my GetTeamData.cs I have a timer that is set to run every 30 seconds
public class GetTeamData
{
static Timer TeamDataTimer = new Timer();
public static void TeamDataRepeater()
{
TeamDataTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent_TeamDataBroadcaster);
TeamDataTimer.Interval = 30000; //30 Seconds
TeamDataTimer.Start();
}
public static void OnTimedEvent_TeamDataBroadcaster(Object sender, ElapsedEventArgs e)
{
updateFirstRow();
}
public static void updateFirstRow()
{
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MsgHub>();
hubContext.Clients.All.pushMyData(mydata1, mydata2, mydata3);
}
}
My java script for the client has:
//I have already started my connection
$(function () {
var chat = $.connection.msgHub;
chat.client.pushMyData = function (mydata1, mydata2, mydata3)
{
//Do something with the returned data now
}
});
Note that I have removed some things such as use of try/catch just to give you an example.
Hope that helps.

SignalR - Passing back a Client's response to the server

I am calling a client-side method from the server that is essentially a Javascript confirm dialog. If the client clicks theCancel button on the dialog I want to delete their token from local storage (essentially logging them out) ...
sessionStorage.removeItem('access-token');
If the client clicks the OK button I want to do the same on a different client (I have the connectionId of this client stored on the server side). So I need a way for the server to know which option the client chose (OK or Cancel).
How would I go about taking the client's choice and passing it back to the server, so the server could call the "Logout" Javascript on the proper client?
I think you have a couple of options.
You could send a message back to the Hub once the user has clicked on a button.
Hub
[HubName("message")]
public class MessageHub : Hub
{
public void sendmessage(bool logout)
{
Clients.Caller(????).logoutuser(logout); //However you identify who to logout??
}
}
Client
var hub = $.connection.message;
hub.client.logoutuser = function(message) {
if (message.logout = true) {
sessionStorage.removeItem('access-token');
}
}
hub.server.sendmessage(true); //result of the user's click OK -> True, Cancel -> False
$.connection.hub.start().done(function () { });
Or you could hit an API that gets the connection for some? user who you're wanting to log out.
API
[RoutePrefix("api/messaging")]
public class MessagingController : ApiController
{
[Route("")]
public void Post(Message message)
{
var notificationHub = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
if (notificationHub != null)
{
try
{
notificationHub.Clients.User(message.UserName).logoutuser(message);
}
catch (Exception ex)
{
}
}
}
}
Client
function notifyController(responseObj) {
$.ajax({
url: '/api/Messaging/',
type: 'POST',
data: responseObj, // some object containing the users response information?
success: function (data) { return; },
error: function (ex) { return; }
});
}

SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*

In the latest version of Asp.Net SignalR, was added a new way of sending a message to a specific user, using the interface "IUserIdProvider".
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
My question is: How do I know to whom I am sending my message? The explanation of this new method is very superficial. And the draft Statement of SignalR 2.0.0 with this bug and does not compile. Has anyone implemented this feature?
More Info : http://www.asp.net/signalr/overview/signalr-20/hubs-api/mapping-users-to-connections#IUserIdProvider
Hugs.
SignalR provides ConnectionId for each connection. To find which connection belongs to whom (the user), we need to create a mapping between the connection and the user. This depends on how you identify a user in your application.
In SignalR 2.0, this is done by using the inbuilt IPrincipal.Identity.Name, which is the logged in user identifier as set during the ASP.NET authentication.
However, you may need to map the connection with the user using a different identifier instead of using the Identity.Name. For this purpose this new provider can be used with your custom implementation for mapping user with the connection.
Example of Mapping SignalR Users to Connections using IUserIdProvider
Lets assume our application uses a userId to identify each user. Now, we need to send message to a specific user. We have userId and message, but SignalR must also know the mapping between our userId and the connection.
To achieve this, first we need to create a new class which implements IUserIdProvider:
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
// your logic to fetch a user identifier goes here.
// for example:
var userId = MyCustomUserClass.FindUserId(request.User.Identity.Name);
return userId.ToString();
}
}
The second step is to tell SignalR to use our CustomUserIdProvider instead of the default implementation. This can be done in the Startup.cs while initializing the hub configuration:
public class Startup
{
public void Configuration(IAppBuilder app)
{
var idProvider = new CustomUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
}
Now, you can send message to a specific user using his userId as mentioned in the documentation, like:
public class MyHub : Hub
{
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
}
Here's a start.. Open to suggestions/improvements.
Server
public class ChatHub : Hub
{
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
Clients.Group(name).addChatMessage(name, message);
Clients.Group("2#2.com").addChatMessage(name, message);
}
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
Groups.Add(Context.ConnectionId, name);
return base.OnConnected();
}
}
JavaScript
(Notice how addChatMessage and sendChatMessage are also methods in the server code above)
$(function () {
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
// Create a function that the hub can call to broadcast messages.
chat.client.addChatMessage = function (who, message) {
// Html encode display name and message.
var encodedName = $('<div />').text(who).html();
var encodedMsg = $('<div />').text(message).html();
// Add the message to the page.
$('#chat').append('<li><strong>' + encodedName
+ '</strong>: ' + encodedMsg + '</li>');
};
// 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();
});
});
});
Testing
This is how use SignarR in order to target a specific user (without using any provider):
private static ConcurrentDictionary<string, string> clients = new ConcurrentDictionary<string, string>();
public string Login(string username)
{
clients.TryAdd(Context.ConnectionId, username);
return username;
}
// The variable 'contextIdClient' is equal to Context.ConnectionId of the user,
// once logged in. You have to store that 'id' inside a dictionaty for example.
Clients.Client(contextIdClient).send("Hello!");
Look at SignalR Tests for the feature.
Test "SendToUser" takes automatically the user identity passed by using a regular owin authentication library.
The scenario is you have a user who has connected from multiple devices/browsers and you want to push a message to all his active connections.
Old thread, but just came across this in a sample:
services.AddSignalR()
.AddAzureSignalR(options =>
{
options.ClaimsProvider = context => new[]
{
new Claim(ClaimTypes.NameIdentifier, context.Request.Query["username"])
};
});
For anyone trying to do this in asp.net core. You can use claims.
public class CustomEmailProvider : IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
return connection.User?.FindFirst(ClaimTypes.Email)?.Value;
}
}
Any identifier can be used, but it must be unique. If you use a name identifier for example, it means if there are multiple users with the same name as the recipient, the message would be delivered to them as well. I have chosen email because it is unique to every user.
Then register the service in the startup class.
services.AddSingleton<IUserIdProvider, CustomEmailProvider>();
Next. Add the claims during user registration.
var result = await _userManager.CreateAsync(user, Model.Password);
if (result.Succeeded)
{
await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.Email, Model.Email));
}
To send message to the specific user.
public class ChatHub : Hub
{
public async Task SendMessage(string receiver, string message)
{
await Clients.User(receiver).SendAsync("ReceiveMessage", message);
}
}
Note: The message sender won't be notified the message is sent. If you want a notification on the sender's end. Change the SendMessage method to this.
public async Task SendMessage(string sender, string receiver, string message)
{
await Clients.Users(sender, receiver).SendAsync("ReceiveMessage", message);
}
These steps are only necessary if you need to change the default identifier. Otherwise, skip to the last step where you can simply send messages by passing userIds or connectionIds to SendMessage. For more

periodically sending messages to all clients using signalr

I want to send some data from server to all connected clients using hubs after a specific interval. How can I accomplish this using signalr hubs.
Spin up the System.Threading.Timer, and from it's callback broadcast the message using specific hub.
Global.asax:
private Timer timer;
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHubs("~/signalr2");
timer = new Timer(TimerCallback(timerCallback), null, Timeout.Infinite, 1000);
}
}
Check the “Broadcasting over a Hub from outside of a Hub” section in SignalR wiki page.
Use ReactiveExtensions and then setup an Observable.Interval call. Then reactive will automatically call the lambda which can broadcast to your clients.
I have stumbled upon this post by Jason Roberts => http://dontcodetired.com/blog/post/Using-Server-Side-Timers-and-SignalR-in-ASPNET-MVC-Applications.aspx
He uses IRegisteredObject and HostingEnvironment.RegisterObject then a System.Threading.Timer in the class that does the work, I haven't tried it myself, but it looks exactly the sort of thing.
Just add
Thread.Sleep(5000);
in your send Method.
Ex:
public void Send(string name, string message)
{
Thread.Sleep(5000);
//call the broadcast message to upadate the clients.
Clients.All.broadcastMessage(name, message);
}
Hope it helps.
Edit
The following code renders the current time for every 5 seconds.
Here is script for it:
<script type="text/javascript">
$(function () {
$.connection.hub.logging = true;
$.connection.hub.start();
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
//Appending the responce from the server to the discussion id
chat.client.currentTime = function (time) {
$('#discussion').append("<br/>" + time + "<br/>");
};
// Start the connection.
$.connection.hub.start().done(function () {
//Call the server side method for every 5 seconds
setInterval(function () {
var date = new Date();
chat.client.currentTime(date.toString());
}, 5000);
});
});
</script>
<div id="discussion"></div>
And on the HubClass write the following:
public class ChatHub: Hub
{
public void currentTime(string date)
{
Clients.All.broadCastTime(date);
}
}

Resources