On server side I have
public class Global : System.Web.HttpApplication
{
private static List<UserInfo> _Users;
public static List<UserInfo> Users
{
get { return Global._Users; }
set
{
lock (_Users)
{
Global._Users = value;
}
}
}
...
public class RawHub : PersistentConnection
{
protected override Task OnConnected(IRequest request, string connectionId)
{
var UserID = request.QueryString.Where(t => t.Key == "U");
if (!string.IsNullOrEmpty(UserID.First().Value))
{
var ui = new Guid(UserID.First().Value);
var user = Global.Users.FirstOrDefault(x => x.USERID == ui);
if (user == null)
{
var us = new UserInfo();
us.USERID = ui;
us.ConnectionId = connectionId;
Global.Users.Add(us);
}
else
{
user.ConnectionId=connectionId;
}
return Connection.Send(user.ConnectionId,"Good To Go");
}
}
I want to tryout send users their specific infos user by user with Send ...
bu I have to clean up Global.Users when they are disconnected.
Q: Is that possible to catch pings ? from js client or must i have to ping to client to detect live or not ?
What is the best approach
thnks
May br you can send custom i am alive messages from js client. Becouse some times ping doesnot come from client as expected. Usually when network is busy this happens...
good luck
Not sure what exactly is the problem here but you can simply listen for messages/pings in OnReceived
asp.net/signalr
Related
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/
I just created a sample project with signalR. I am just trying to test managing multiple connection. Everything works as expected when I open the first browser and load the page. It is going to fire the OnConnected event on the hub. But when I open another browser or different tab and load the page, it doesn't fire OnConnected event anymore. It shows $.connection.hub.id though.
Here is the hub
[HubName("genie")]
public class Genie : Microsoft.AspNet.SignalR.Hub
{
private static ConnectionManager _manager = new ConnectionManager();
[HubMethodName("AdminCommand")]
public void AdminCommand(string command, string message = "")
{
var connetions = _manager.GetConnections();
connetions.Remove(Context.ConnectionId);
Clients.Clients(connetions).onAdminCommand(command, message);
}
public override Task OnConnected()
{
_manager.AddConnection(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
_manager.RemoveConnection(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
}
And here is the javascript code:
var proxy = $.connection.genie;
$.connection.hub.start()
.done(function (state) {
console.log($.connection.hub.id);
});
proxy.on('onAdminCommand', function (command, message) {
if (command == "HappyGenie") {
$scope.goTo("happy/");
} else if (command == "SadGenie") {
$scope.goTo("sad/");
} else if (command == "CustomAnnouncement") {
dataService.setDataByKey("Announcement", message);
$scope.goTo("customannouncement/");
}
});
I establish a connection with the generated proxy.
Is there something I am doing wrong?
Thanks
I'm hosting my signalr hub on a separate domain and making cross domain connection to hub from my main application. When a user logs into the main application, signalr connection is established. Now, the problem I'm having is how to identify the connected user inside the hub.
If my Hub was within the main application then I could use the Context.User of the logged in user and maintain a dictionary and update them on Connect and Disconnect events.
But being a cross-domain connection, I don't have the Context.User and no way for me to know to whom that connection ID belongs to. I'm lost here.
What am I missing here?
You should keep users credentials and connections ids yourself. You should define List<ClientsEntity> or something like that. Then override onConnected and onDisconnected methods. Client has to send querystring for connecting to your Hub as Lars said.
for example clients send to you like this
$.connection.hub.qs = { 'token' : 'id' };
In the Hub Class:
public class ChatHub : Hub
{
static List<ClientsEntity> clientsList = new List<ClientsEntity>();
public override Task OnConnected()
{
string connectionID = Context.ConnectionId;
string token = Context.QueryString["token"];
ClientsEntity clientItem = new ClientsEntity();
clientItem.connectionId = connectionID;
clientItem.token = token;
clientItem.connectionTime = DateTime.Now;
clientsList.Add(clientItem);
return base.OnConnected();
}
public override Task OnDisconnected()
{
ClientsEntity item = clientsList.FirstOrDefault(c => c.connectionId == Context.ConnectionId);
if (item != null) {
clientsList.Remove(item);
}
return base.OnDisconnected();
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
public void Send(string token, string message)
{
ClientsEntity user = clientsList.FirstOrDefault(c => c.token == token);
if (user != null)
Clients.Client(user.connectionId).sendMessage(token, message);
}
public void GetConnectedClients(string token) {
ClientsEntity user = clientsList.FirstOrDefault(c => c.token == token);
if(token.Equals("-1") && user != null)
Clients.Client(user.connectionId).getConnClients(clientsList);
}
}
You could assign a unique connection token to the user once they log in; then make the client send that in the query string:
$.connection.hub.qs = { 'token' : id };
I have a class that inherits PersistentConnection. When I override OnConnected I check a few of the querystring parameters passed in to make sure the user is authenticated. If not I throw an exception but the client is still considered connected. How can I remove the client from the connected clients list?
public class NotificationConnection : PersistentConnection
{
protected override Task OnConnected(IRequest request, string connectionId)
{
if (String.IsNullOrWhiteSpace(request.QueryString["example"]))
throw new SecurityException("whatever");
return base.OnConnected(request, connectionId);
}
protected override Task OnDisconnected(IRequest request, string connectionId)
{
return base.OnDisconnected(request, connectionId);
}
}
Consider changing your design to use the method exposed by signalr to validate users are authenticated and they have rights on the Persistent Connection
protected override bool AuthorizeRequest(IRequest request)
{
return request.User != null && request.User.Identity.IsAuthenticated;
}
Why don't you just send a message back to the client telling it to disconnect? e.g.
On the server.
if (String.IsNullOrWhiteSpace(request.QueryString["example"]))
{
Connection.Send(connectionId, "Close");
}
Then on the JS client do something like;
connection.received(function(data) {
if ( data === "Close" ){
connection.stop();
// send the user to another page with window.location or warn them that their connection has been stopped.
}
});
On a .net client;
connection.Received += data =>
{
if ( data == "Close" )
{
connection.stop();
}
};
Currently, I am storing all connected user's connection ids inside my database by mapping them to actual application users. What I do is pretty simple here: I add the connection id to the database when OnConnected event is fired. Then, I remove that connection from the database when OnDisconnected event is fired.
However, at some cases (for example, when the process is terminated, etc.), I don't get the disconnect event. This makes my connection table unreliable because I cannot be sure if the user is connected on one or more clients. For example, here is a block of code on my OnDisconnected method:
HubConnection hubConnection = _hubConnectionRepository.GetAll()
.FirstOrDefault(conn => conn.ConnectionId == connectionId);
if (hubConnection != null)
{
_hubConnectionRepository.Delete(hubConnection);
_hubConnectionRepository.Save();
}
if (!_hubConnectionRepository.GetAll().Any(conn => conn.UserId == user.Id))
{
Clients.Others.userDisconnected(username);
}
As you see, I check if there is any other connections associated to that user just after I remove his/her current connection. Depending on the case, I broadcast a message to all connected clients.
What I want here is something like this: to be able to poll the SignalR system with an array of connection ids and get back the disconnected ones so that I can remove them from my connection list inside the database. As far as I remember from my conversation with David Fowler, this's not possible today but what's the preferred approach on such cases?
This is just an idea.
On server:
Clients.All.ping()
On clients:
hub.client.ping = function() {
hub.server.pingResponse();
}
On Server:
void pingResponse()
{
Context.ConnectionId; //update database
}
This is what I did:
I have a class HubConnectionManager:
public class HubConnectionManager
{
static HubConnectionManager()
{
connections = new Dictionary<string, List<string>>();
users = new List<Login>();
}
#region Static Fields
private static Dictionary<string, List<string>> connections;
private static List<Login> users;
#endregion
#region Public Properties
public static Dictionary<string, List<string>> Connections
{
get
{
return connections;
}
}
#endregion
#region Public Methods and Operators
public static void AddConnection(Login login, string connectionId)
{
if (!connections.ContainsKey(login.LoginName))
{
connections.Add(login.LoginName, new List<string>());
if (!users.Contains(login))
{
users.Add(login);
}
}
// add with new connection id
connections[login.LoginName].Add(connectionId);
}
public static bool IsOnline(string connectionId)
{
return connections.Any(x => !string.IsNullOrEmpty(x.Value.FirstOrDefault(y => y == connectionId)));
}
public static void RemoveConnection(string user, string connectionId)
{
if (connections.ContainsKey(user))
{
connections[user].Remove(connectionId);
if (connections[user].Count == 0)
{
connections.Remove(user);
// remove user
users.RemoveAll(x => x.LoginName == user);
}
}
}
public static int GetAllConnectionsCount()
{
return connections.Keys.Sum(user => connections[user].Count);
}
public static Login GetUser(string connectionId)
{
string userName = connections.FirstOrDefault(x => x.Value.Any(y => y == connectionId)).Key;
return users.FirstOrDefault(x => x.LoginName == userName);
}
#endregion
}
I'm using a dictionary that holds UserName and it's list of connections (this is because like you said sometimes OnDisconnected doesn't fire properly:
connections = new Dictionary<string, List<string>>();
Then in your hub, you can check if a connection is still "connected"/ valid:
public class TaskActionStatus : Hub
{
public void SendMessage()
{
if (HubConnectionManager.IsOnline(Context.ConnectionId))
{
this.Clients.Client(Context.ConnectionId).actionInit("test");
}
}
...
}