I've a disconnect method in TestHub(which is inherited by Hub) class for signal R. I'm not able to call javascript method fnDeleteCustomer from OnDisconnected method, however same same js method gets called on Connect method. What I'm doing wrong?
public override Task OnDisconnected()
{
try
{
var customer = ConnectedUsers.Find(x => x.ConnectionID == Context.ConnectionId);
if (customer!=null)
{
Clients.Client(customer.ConnectionID).fnDeleteCustomer(customer.UserId);
return base.OnDisconnected();
}
}
catch { };
return null;
}
According to MSDN:
Occurs when a connection disconnects from this hub instance.
So you have no any alive connection and you cannot access the client hub and its methods.
I suppose you should use client-side disconnected event:
$.connection.hub.disconnected(function() {
$.connection.hub.fnDeleteCustomer(userId);
});
More information about signalr lifetime events can be found here.
You are not able to execute fnDeleteCustomer because at the moment of executing OnDisconnected, the client had already disconnected from the hub, so in that moment, the client wil not have a ConnectionId.
Sure, you can use the client disconnected method, but the SignalR disconnection mostly happens when the client leaves the respective page.
From my point of view, it is not the client who just left that you want to execute the fnDeleteCustomer method, but the remaining ones so they can be notified that somebody left.
Hope this helps! Best of luck!
EDIT:
If you want to notify all the other clients that someone left, you simply do so:
public override OnDisconnected()
{
var customer = ConnectedUsers.Find(x => x.ConnectionID == Context.ConnectionId);
Clients.All.notifySomeoneLeft(customer.Name);
}
Then, you create your client method notifySomeoneLeft:
$.connection.client.notifySomeoneLeft = function(customerName){
alert(customerName + "just left!");
};
And that's it. Everytime someone leaves, all the connected clients will get an alert.
Best of luck!
public override OnDisconnected()
{
var customer = ConnectedUsers.Find(x => x.ConnectionID == Context.ConnectionId);
Clients.All.notifySomeoneLeft(customer.Name);
}
The context.connectionId is getting new connectionId instead of old one.
Related
I am creating web application, which let's users to communicate with so called chat.
In order to enable such communication, I use SignalR library. I create connection at first visit to my page (main page). So JS code, which creates connection, creates variable used to configure connection.
Then user enters chat room, which is different page, so new JSs are loaded etc. The one, which held connection variable is now unavailable. But now I need that connection to send messages in my chat room.
So this variable must be "transfered" over to next scripts.
So, what would be the way to actually persist connection through whole session on the website?
Finally I got my answer.
It was suggested that I should use ASP NET Identity, which is very valid, but I already created simple authentication and users management. It isn't safe and as half good as ASP NET Identity (I looked throught it and got my head around it), but it's just personal project, not production one, so if it evolves I might switch to Identity or even implement one myself ;) But it's not the case.
It required little bit of extra steps, so:
I needed to enable sessions in ASP.NET Core, I used this article for this purpose. Having that, I can persist my user login and provide it to user ID provider for signalR
Adding cutsom user ID provider for SignalR in .NET:
I needed to create such class
public class UserIdProvider : IUserIdProvider
{
public static readonly string SESSION_LOGIN_KEY = "loggedUser";
private readonly IHttpContextAccessor _httpContextAccessor;
public UserIdProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public string GetUserId(HubConnectionContext connection)
{
var session = _httpContextAccessor.HttpContext.Session;
session.TryGetValue(SESSION_LOGIN_KEY, out byte[] loginBA);
if (loginBA == null)
{
return null;
}
return new string(loginBA.Select(b => (char)b).ToArray());
}
}
So, after logging I can set login in Session, so it becomes "state" variable, and use it in above class.
Also, one need to add it in ASP services, like below (in Startup.ConfigureServices):
services.AddSingleton<IUserIdProvider, UserIdProvider>();
There also one thing, which still need to be set: in UserIdProvider we need to access Session through HttpContext. In order to use HttpContextwe need to specify it like below (also in Startup.ConfigureServices):
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
which passes HttpContextAccessor to serices constructors.
After all this, you can access users in SignalR Hub with their logins, which is set in Context.UserIdnentifier
This also enables sending messages to specific user, just by passing their login (frontend client just chooses user), like below:
public async Task SendMessage(string message, string user)
{
await Clients.User(user).SendAsync("ReceiveMessage", message).ConfigureAwait(false);
}
NOTE There was one problem though. Browsers on computer didn't persist sessions, which I solved by (also in Startup.ConfigureServices):
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false; // <~~~~ This needs to be set to false
options.MinimumSameSitePolicy = SameSiteMode.None;
});
Without it, you need to be carefull with cookies, if you don't accept them on a site, it won't work, as user's login won't be persisted.
On the server side, you can send the message to specific user via user id:
var user = await _userManager.GetUserAsync(Context.User);
await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
On the client side, whenever the connection is started and the hub msg is listened, the user will receive the message:
connection.on('msg', function (...data) {
console.log('data:', data);
});
By sending message(s) via user id, you don't need to care where the target user is.
public class ChatHub : Hub
{
private IUserManager<ApplicationUser> _userManager;
public ChatHub(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task GetInfo()
{
var user = await _userManager.GetUserAsync(Context.User);
await Clients.User(user.Id).SendCoreAsync("msg", new object[] { user.Id, user.Email });
}
}
So, here is my issue:
I might be doing something wrong but what I basically trying is, connecting two peers with a video chat while making text conversation.
Here's my ChatHub.cs
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(name, message);
}
JS part of it
chat.client.broadcastMessage = function (name, message) { //my function does its job }
This works very well but for video call in ChatHub.cs some function in ChatHub calls
public void GetConnectionIDandSet()
{
var users = JsonConvert.SerializeObject(ConnectedUsers);
Clients.All.getConnectionIDandSet(users);
}
and corresponding JS part of it
chat.client.getConnectionIDandSet = function (users) { // do the job }
and when i try to debug it on console
Clients.All.getConnectionIDandSet(users); does not trigger
chat.client.getConnectionIDandSet = function (users) { // do the job }
So, what am I doing wrong at here?
The gist for full project is here.
Mistake:
(According code on GitHub): You must set querystring bevor you start the connection. See: learn.microsoft.com/en-us/aspnet/signalr/overview/….
Additional hint:
For readabily of your code I suggest you to define all methods which the server can call on the client bevor your start the connection
I have been trying to implement signalR into my game to handle updating player position real time, and broadcasting that to all connected clients.
However, my client function which is invoking the server hub method does not ever reach the server, unless I call it directly after $.connection.hub.start().done(). Let me give you an example...
This calls the server hub method UpdatePlayerPosition correctly, but I don't want to call this method directly on .done:
$.connection.hub.start().done(function () {
$.connection.playerHub.server.updatePlayerPosition(playerObj)
});
However, if I create a variable on my main gameEngine object and call it later, I.E each time a player moves, nothing happens.
$.connection.hub.start().done(function () {
gameEngine.updatePlayerPosition = function () {
$.connection.playerHub.server.updatePlayerPosition(playerObj)
//playerObj is a global variable
}
});
if (player.isMoving) {
gameEngine.updatePlayerPosition();
}
Calling gameEngine.updatePlayerPosition() does hit the correct function, and goes through all of the signalr.js, but the breakpoint on the server is not hit.
Here is the hub code (I have not added the functionality for updating the player on the hub yet, I just want to get the communication going first)
using Microsoft.AspNet.SignalR;
using RPGGameCS.Models.Player;
namespace RPGGameCS
{
public class PlayerHub : Hub
{
public void UpdatePlayerPosition(IPlayer player)
{
Clients.All.updatePlayerPosition(player);
}
}
}
All the examples of signalR I've seen bind these server calls to click events, which I don't want to do. It seems like this should be possible, but nothing I've tried thus far has worked. Is there something I'm doing wrong? Or maybe it's not possible to delegate singalR functions in this way?
EDIT 1***: So it appears it has something to do with the player object I'm sending to the hub method. When I make the player object null, the communication appears to work fine. But when I send the actual player object, the break point is never hit.
Method signature must match.
In your hub you have a parameter IPlayer and on client you do not set this parameter. gameEngine.updatePlayerPosition(); does not matcht with public void UpdatePlayerPosition(IPlayer player)
If you call for example gameEngine.updatePlayerPosition(); signalr searchs for a method on the connected hub according name and parameters. In the case it will not find a method I think you should find this in the log.
Enable log for testing:
$.connection.hub.logging = true;
$.connection.hub.start();
(https://learn.microsoft.com/en-us/aspnet/signalr/overview/testing-and-debugging/enabling-signalr-tracing)
I have an asp.net mvc site where users can select a number of objects, they are identified by a unique id.
What I would like to do is:
User A opens an object
When user B tries to open the same object, a message will appear saying this is already open by user A, therefore is inaccessible.
I guess the server would have a sort of 'shared' session that can be queried in every Ajax request, to check if the object is already open by a different user. When the object is closed then that id should be removed from the shared list of opened objects. What would be the best approach for this?
In a typical scenario, assuming that you are working with plain CLR objects, this could be a solution
// User1 and User2 are trying to access objectA
private static int objectACounter; // Counter to decide whether object A is occupied
if(objectACounter != 0) // Object A is occupied
return user message object A is occupied
lock(objectA)
{
objectACounter ++;
// User logic processing
objectACounter --;
}
Above mentioned solution can be further refined by using a static dictionary to suggest which user has currently locked the object A and at what time, which can be returned to the incoming user.
In this case you plan to use something like Shared session, in my understanding there's nothing like a shared session, since session is user specific, above mentioned mechanism is a way to create one. You can plan to use Application Cache instead, where for each object you can define a key for each object and add the details in same way as you would do to the static dictionary, since Cache is a key value pair and it persist across the multiple user requests on the server.
Logic will be something like:
if(Cache.ContainsKey(objectAKey))
// Return error to the incoming user with details of object holding user
lock(objectA)
{
Cache.Insert(objectAKey, UserValues);
// User1 logic processing
Cache.Remove(objectAKey, UserValues);
}
Please let me know if I have misunderstood your question and you are expecting a different solution, We can adapt the current solution in multiple scenarios
I had to do something similar recently, but what I ended up doing was to not prevent a second person from accessing a submitted application, but to notify them that someone else is currently working on it when they view it (passive feedback) and if they attempt to modify it, prevent them from doing so, either after the postback occurs and possibly rendering the UI in a readonly fashion (active feedback).
I tracked the activity of who was using what with SignalR, this way I could dynamically update the list of applications when someone accessed one for the first time, or release an application when they leave the page, without forcing the user viewing the list of projects to refresh the page to be made aware of the change.
I setup a signalR Hub in my code to track which project was claimed by whom. I implemented the tracking using ConcurrentDictionary class to tackle any sort of concurrency problems of modifying the collection.
public class RetainedApplicationsHub : Hub
{
private static readonly ConcurrentDictionary<Guid, RetainedApplication> RetainedApplications = new ConcurrentDictionary<Guid, RetainedApplication>();
public static bool IsApplicationRetained(Guid applicationId)
{
return RetainedApplications.ContainsKey(applicationId);
}
public void RetainApplication(Guid applicationId, Guid personId)
{
var retainedSuccessfully = RetainedApplications.TryAdd(applicationId, new RetainedApplication{ConnectionId = Context.ConnectionId, PersonId = personId});
if(retainedSuccessfully)
Clients.All.applicationRetained(applicationId);
}
public void ReleaseApplication(Guid applicationId)
{
RetainedApplication temp;
RetainedApplications.TryRemove(applicationId, out temp);
Clients.All.applicationReleased(applicationId);
}
public ICollection<Guid> GetRetainedApplicationIds()
{
return RetainedApplications.Keys;
}
public override Task OnDisconnected(bool stopCalled)
{
RetainedApplication temp;
var applicationId = RetainedApplications.Single(x => x.Value.ConnectionId == Context.ConnectionId).Key;
RetainedApplications.TryRemove(applicationId, out temp);
Clients.All.applicationReleased(applicationId);
return base.OnDisconnected(stopCalled);
}
}
public class RetainedApplication
{
public string ConnectionId { get; set; }
public Guid PersonId { get; set; }
}
On my Application list page
<script src="~/signalr/hubs"></script>
<script>
$(function () {
'use strict';
var retainedApplicationsHub = $.connection.retainedApplicationsHub;
retainedApplicationsHub.client.applicationRetained = function(applicationId) {
$("td:contains('" + applicationId + "')").parent().addClass("active text-muted");
};
retainedApplicationsHub.client.applicationReleased = function(applicationId) {
$("td:contains('" + applicationId + "')").parent().removeClass();
};
$.connection.hub.start().done(function() {
retainedApplicationsHub.server.getRetainedApplicationIds()
.done(function(applicationIds) {
$.each(applicationIds, function() {
retainedApplicationsHub.client.applicationRetained(this);
});
});
});
});
</script>
On my application details page
<script src="~/signalr/hubs"></script>
<script>
$(function () {
'use strict';
var retainedApplicationsHub = $.connection.retainedApplicationsHub;
retainedApplicationsHub.client.applicationRetained = function(applicationId) {
//do nothing, must subscribe to at least one event to have the OnDisconnected event on the hub register correctly
};
$.connection.hub.start().done(function () {
retainedApplicationsHub.server.retainApplication("#Model.RegistrationApplication.ObjectId", "#User.GetIdentityId()");
});
});
</script>
As you can see in Facebook's notification system,When an specified change related with your activities happens,Facebook notifies it to you.
The important issue is that if we want to use SignalR to send notifications,we have 2 choices:
Using the Client side to call a server method who broadcast a
message to the Clients
Using the Server side to Start a connection and Invoke a method in
server side who broadcast a message to the Clients as bellow :
[HubName("messenger")]
public class MessengerHub : Hub
{
/// <summary>
/// Broads the cast message.
/// </summary>
/// <param name="message">The message.</param>
public void BroadCastMessage(Object message, string group)
{
_messenger.BroadCastMessage(message, group);
}
}
And here,we Invoke the BroadCastMessage method from Server to Notify Users :
var connection = new HubConnection("http://localhost:21600/");
SignalRConsole.SignalR.MessengerHub.Message message = new SignalR.MessengerHub.Message();
message.Content = Console.ReadLine();
message.Duration = 500;
var myHub = connection.CreateProxy("messenger");
connection.Start().Wait();
myHub.Invoke("broadCastMessage", myData);
Console.ReadLine();
Now,if we want to Broadcast a message as users Notifications based on the last changes in Database we should frequently check the Database and then start a connection to call a broadcast method to send notifications to the users.Then, the way i know for this continuous checking is using an infinite while loop same as bellow :
> while (true){
> //Some codes for tracing the last changes in Database such as new related events
> if(CheckIfThereIsaNewEvent()){
> connection.Start().Wait();
> myHub.Invoke("broadCastMessage", myData);
}
> }
This coding style should be terrible when we have a hundred millions of users because server should continuously calls a method and checks DataBase to the new events and broadcast a message to the vast array of clients,therefore we should allocate a huge computations and hence a huge load on the server and it is not logical.
So,what is the true method to do this? What is the Facebook's solution for this problem?
Here we can trigger a message broadcast while database update. for example we have below method and db update method
[HubName("messenger")]
public class MessengerHub : Hub
{
/// <summary>
/// Broads the db update message.
/// </summary>
/// <param name="message">The message.</param>
public void BroadCastDbUpdate(Bool IsDbUpdate, string changes)
{
_messenger.BroadCastMessage(IsDbUpdate, changes);
}
}
public void DbUpdate()
{
//Do your db update and call the broadcast method
_messenger.BroadCastMessage(IsDbUpdate, changes);
}
Try using SqlDependency class OnChange Event to fire the SignalR method. You can specify on which query the Event should be fired :)