I want to be able to have individual users send messages to each other using SignalR, therefore I need to send to a Specific Client ID. How can I define the client ID for a specific user at the start of the session - say a GUID Primary Key for the user?
Replace the IConnectionIdFactory with your own https://github.com/SignalR/SignalR/wiki/Extensibility.
Sample usage:
http://www.kevgriffin.com/maintaining-signalr-connectionids-across-page-instances/
EDIT: This is no longer supported in the latest versions of SignalR. But you can define a user id for a specific connection using the new IUserIdProvider
In SignalR version 1, using the Hubs approach, I override the Hub OnConnected() method and save an association of a .NET membership userId with the current connection id (Context.ConnectionId) in a SQL database.
Then I override the Hub OnDisconnected() method and delete the association between the .NET membership userId and the current connection id. This means, on a page reload, the userId/connectionId association will be up-to-date.
Something along the lines of:
public class MyHub : Hub
{
private MembershipUser _user
{
get { return Membership.GetUser(); }
}
private Guid _userId
{
get { return (Guid) _user.ProviderUserKey; }
}
private Guid _connectionId
{
get { return Guid.Parse(Context.ConnectionId); }
}
public override Task OnConnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Create(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnConnected();
}
public override Task OnDisconnected()
{
var userConnectionRepository = new UserConnectionRepository();
userConnectionRepository.Delete(_userId, _connectionId);
userConnectionRepository.Submit();
return base.OnDisconnected();
}
}
Then when I need to trigger a SignalR event for a specific user, I can work out the connectionId from the database association(s) with the current userId - there may be more than one association if multiple browser instances are involved.
The SignalR Client Side documentation outlines the following:
connection.id
- Gets or sets the client id for the current connection
This certainly indicates that one should be able to set the clientID client side, without all the above plumbing. Is this not working? If working, how would this line of code look like?
Related
We are trying to integrate SignalR in an 3rd party application to talk to our Hubs we have for our aspnetboilerplate application. This is using the .NET Core template. We are having an issue with the session in aspnetboilerplate having a null UserId even when getting past the attribute on our Hub to check for authorization.
The issue we are having is at random times the UserId inside of AbpSession will just be null. It gets past the [Authorize] attribute but aspnetboilerplate seems to think the UserId is null at random times. I can invoke a method on our Hub and see the UserId is correct for that user. Then the very next time I invoke that same method on the hub with the same user the UserId inside of AbpSession is null. I can then invoke the method again and the UserId will sometimes be null or sometimes be correct. Their doesn't seem to be any consistency in this issue. Every now and then it will alternate between being null and having the correct UserId.
Our client code:
let connection = new signalR.HubConnectionBuilder()
.withUrl('ENTER HUB URL HERE',
{
transport: signalR.HttpTransportType.LongPolling,
accessTokenFactory: () => {
return 'BEARER TOKEN HERE'
}}).build()
connection.invoke('sendGroupMessage', text, hardCodedChatGroup)
Here is a sample of our SignalR Hub on the server:
[AbpMvcAuthorize]
public class OpenChatHub : Hub, ITransientDependency
{
public IAbpSession AbpSession { get; set; }
public ILogger Logger { get; set; }
public OpenChatHub()
{
AbpSession = NullAbpSession.Instance;
Logger = NullLogger.Instance;
}
public async Task SendGroupMessage(string message, string groupName)
{
// logic for the SendGroupMessage would be here
var msg = new
{
sendById = AbpSession.UserId, // this will be null at random times
message = message
};
await Clients.Group(group).SendAsync("receiveChatMessage", msg);
}
}
I can view the requests for SignalR negotiating and communicating with the Hub and I can see the token being passed correctly each time.
After doing a bit more research on this while trying to get a test project together that I could put on GitHub to reproduce the issue I did end up solving the issue.
Using the following inside of our Hub gives us the correct UserId each time now. Context.User.Identity.GetUserId();
I believe this must be a bug inside of aspnetboilerplate now. I will be trying to get an issue reported on the GitHub.
I'm new to signalr and created a project with this sample to get the number of users on a specific website: Tutorial
This is running fine. My goal is to access the website only by one user, if a second user want to open the page he should be redirected. How can I do this?
If I check the users on the page and redirect if there are more than one then all users get redirected. Ok that what signalr should do.
userActivity.client.updateUsersOnlineCount = function (count) {
// Add the message to the page.
$('#usersCount').text(count);
if (count > 1) { window.document.location.href = "OPL.aspx"; }
};
How can I store the count in a datatype which I can access from code behind in the .cs? Thanks
For this, you need two client methods. The updateUsersOnlineCount have one job, which is to update users online for all to see. Then you need a second client side method called something like redirectTheUser to redirect the user.
In your SignalR hub, you would implement the OnConnected, OnReconnected, OnDisconnected events, to store (keep track of) the connection Ids, and when the count reaches a certain threshold, send the updateUsersOnlineCount to all clients with Clients.All.updateUsersOnlineCount(msg), but send the message withClients.Client(connectionId).redirectTheUser()` for all users above the threshold.
To illustrate:
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
// send to all above threshold
if(_connections.Count > threshold)
SendRedirect(_connections.Skip(threshold));
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string name = Context.User.Identity.Name;
_connections.Remove(name, Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
if (!_connections.GetConnections(name).Contains(Context.ConnectionId))
{
_connections.Add(name, Context.ConnectionId);
// send to all above threshold
if(_connections.Count > threshold)
SendRedirect(_connections.Skip(threshold));
}
return base.OnReconnected();
}
private void SendRedirect(IEnumerable<string> connectionIds)
{
foreach (var connectionId in connectionIds)
{
Clients.Client(connectionId).redirectTheUser();
}
}
I’m trying to build a chat apps whereby users id are represented by their auto generated signalR connection id. On page refresh, the connection id changes when a new connection is instantiated. Is there a way to persist the state of the connection id of a user until the browser session is ended (i.e until he ends his session on client).
Any guide or documentation? It really would help.
i am new in signalr. so trying to know many things searching Google. from this url i got a similar snippet http://kevgriffin.com/maintaining-signalr-connectionids-across-page-instances/
they are saying it is possible. the problem is signalr often create a new connection id if we referesh the page. i want to prevent this but how.......
this code snippet.
public class MyConnectionFactory : IConnectionIdFactory
{
public string CreateConnectionId(IRequest request)
{
if (request.Cookies["srconnectionid"] != null)
{
return request.Cookies["srconnectionid"];
}
return Guid.NewGuid().ToString();
}
}
$.connection.hub.start().done(function () {
alert("Connected!");
var myClientId = $.connection.hub.id;
setCookie("srconnectionid", myClientId);
});
function setCookie(cName, value, exdays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = cName + "=" + c_value;
}
my doubt is does it work in all signalr version? if not then how to handle it in new version specially for not generate a new connection id if page gets refreshed. looking for suggestion.
if we work with Persistent connection class instead of hub then what happen.....in this case connection id will persist if we refresh the page at client side? please guide.
thanks
SignalR allows you to send messages to a user via their IPrincipal.Identity.Name. Just use Clients.User(userName) instead of Clients.Client(connectionId).
If you for some reason cannot address a user using their IPrincipal.Identity.Name you could create your own IUserIdProvider. This is the replacement for IConnectionIdFactory which no longer exists in SignalR >= 1.0.0.
The equivalent IUserIdProvider would look like this:
public class MyConnectionFactory : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request.Cookies["srconnectionid"] != null)
{
return request.Cookies["srconnectionid"];
}
return Guid.NewGuid().ToString();
}
}
public class Startup
{
public void Configuration(IAppBuilder app)
{
var idProvider = new MyConnectionFactory();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
app.MapSignalR();
}
}
public class MyHub : Hub
{
public Task Send(string userName, string message)
{
return Clients.User(userName).receive(message);
}
}
It would be really trivial to spoof a user given this MyConnectionFactory. You could make it more secure by using an HMAC.
Ideally you would just use the default IUserIdProvider which retrieves the user ID from IRequest.User.Identity.Name.
The user id provider doesn't work, because the connection id doesn't come from it. I implemented a solution (https://weblogs.asp.net/ricardoperes/persisting-signalr-connections-across-page-reloads) that uses a pseudo-session id stored in session storage. Every SignalR connection id is then mapped to this pseudo-session id.
I have the following JS working:
var chat = $.connection.appHub;
My app has a single hub, AppHub, that handles two types of notifications - Chat and Other. I'm using a single hub because I need access to all connections at all times.
I need to be able to tell OnConnected which type it is via something like the following:
[Authorize]
public class AppHub : Hub {
private readonly static ConnectionMapping<string> _chatConnections =
new ConnectionMapping<string>();
private readonly static ConnectionMapping<string> _navbarConnections =
new ConnectionMapping<string>();
public override Task OnConnected(bool isChat) { // here
string user = Context.User.Identity.Name;
if (isChat){
_chatConnections.Add(user, Context.ConnectionId);
_navbarConnections.Add(user, Context.ConnectionId);
} else{
_navbarConnections.Add(user, Context.ConnectionId);
}
}
}
Usage would ideally be something like this:
var chat = $.connection.appHub(true);
How can I pass that parameter to the hub from javascript?
Update:
SendMessage:
// will have another for OtherMessage
public void SendChatMessage(string who, ChatMessageViewModel message) {
message.HtmlContent = _compiler.Transform(message.HtmlContent);
foreach (var connectionId in _chatConnections.GetConnections(who)) {
Clients.Client(connectionId).addChatMessage(JsonConvert.SerializeObject(message).SanitizeData());
}
}
I would rather add a method to the hub that you call from the client to subscribe to the type. E.g.
public void Subscribe(bool isChat) {
string user = Context.User.Identity.Name;
if (isChat){
_chatConnections.Add(user, Context.ConnectionId);
} else{
_otherConnections.Add(user, Context.ConnectionId);
}
}
You call this method after the hub is connected. It is more flexible in terms that it is then possible to change the notification type without having to reconnect. (Unsubscribe and Subscribe)
Alternative
If you don't want the extra roundtrip/flexibility. You can send QueryString parameters when connecting to the hub. Stackoverflow answer: Signalr persistent connection with query params.
$.connection.hub.qs = 'isChat=true';
And in OnConnected:
var isChat = bool.Parse(Context.QueryString["isChat"]);
Hallvar's answer is useful in most cases. But sometimes you could also use headers to send data to the OnConnected method.
Code example for Asp .Net Framework:
var myParameter = HttpContext.Current.Request.Headers["HeaderName"];
For .NET 5+ you may need Dependency Injection to access HttpContext, as shown here
I received data from different server to my Hub class. Each data has its own ID. Whenever data comes to the server hub, it push my data to the client. This is like job progress.
I want to send each ID to the client with unique hub id., How do I filter the message from the server? I used in this way Clients.Client("ID1").send(data); Or I have to specify in caller property? Anyone can help me.
With Regards,
Shanthini
You can use ConnectionId to identify the client.
When new client is connected, store ConnectionId somewhere so that you can use it later to identify the client.
public class MyHub : Hub
{
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
// store connectionId somewhere
return base.OnConnected();
}
}
To send data to the client, identify it by ConnectionId:
public void SendNewData(string connectionId, object data)
{
var Context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
Context.Clients.Client(connectionId).send(data);
}
If you need to identify clients by some other ID, then you should store relationship between your ID and ConnectionId.