SignalR - sending parameter to OnConnected? - signalr

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

Related

Adding username to groups in SignalR

Is it possible to use the IUserIDProvider instead of ConnectionID when working with Groups? I have already found an answer here, but that concerns the SignalR 1.0 version. I wonder, whether things have changed in 2.0.
So far, I was using the conventional
Groups.Add(Context.ConnectionId, "groupName");
However, it was difficult to keep track of the connected users when their connectionID was changed (the client is a Xamarin Android app and somehow, reconnection always resulted in creation of a new ConnectionID). Thus, when the client is connecting, I have added a header:
public async Task<bool> Login(int waitMilis, string name)
{
var cts = new CancellationTokenSource();
try
{
cts.CancelAfter(waitMilis);
_connection.Headers.Add("userName", name);
await _connection.Start();
return true;
}
catch(Exception ex)
{
CallFailure(ex);
return false;
}
}
And on server side, implemented the IUserIdProvider:
public class MyUserProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
if (request == null)
throw new ArgumentNullException("request");
else if (request.Headers != null && request.Headers["userName"] != null)
return request.Headers["userName"].ToString();
else return null;
}
}
Now, I would like to do something like
Groups.Add("userName", "groupName");
but the Add method does not have an overload for IUserIdProvider. So, is there a possibility to combine the IUserIdProvider and working with Groups, or am I stuck to creating a ConcurrentDictionary and then calling this?
foreach(User user in group.Users)
{
Clients.User(user.Name).SendMessage(message,
group.LastUpdateIndex
);
}
It ruins the whole beauty and simplicity of the SignalR code :-/
Unfortunately, there isn't currently a method like Groups.Add("userName", "groupName"); in SignalR.
I suggest adding users to their appropriate group(s) in OnConnected:
public class MyHub : Hub
{
public override async Task OnConnected()
{
var userName = MyUserHelper.GetUserId(Context.Request);
foreach (var groupName in GroupManager.GetJoinedGroups(userName))
{
await Groups.Add(Context.ConnectionId, groupName);
}
}
// ...
}
If you need to add an already connected user to a group, then you will likely need to send a message to the user using something like Clients.User(userName).joinGroup(groupName). Each client with userName could then call the appropriate hub method to join groupName.

How to persist SignalR connection ID

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.

How to invoke a post when using HubController<T>?

I can't find much documentation on the new HubController<T> so maybe I'm going about this wrong. This is what I have:
public class StatusController : HubController<StatusHub>
{
private string _status = "";
public string Get()
{
return _status;
}
public void Post(string status)
{
_status = status;
// Call StatusChanged on SignalR clients listening to the StatusHub
Clients.All.StatusChanged(status);
}
}
public class StatusHub : Hub { }
This is how I'm attempting to create the hub proxy:
var hubConnection = new HubConnection("http://localhost:51076/");
var statusHubProxy = hubConnection.CreateHubProxy("StatusHub");
statusHubProxy.On<string>("StatusChanged", status => Console.WriteLine("New Status: {0}", status));
await hubConnection.Start();
How do I call the Post method of my controller? This is where I'm getting an exception:
await statusHubProxy.Invoke("Post", "Test Status");
HubController<T> just provides some basic plumbing that gets you access to the resources that are associated with the specific hub type (e.g. Clients) that you want to work with. Calling it has nothing to do with invoking the actual hub itself, so you don't use the hub client API, it's just straight HTTP calls. Without HubController<T> you would have to reach out to SignalR's GlobalHost.Configuration.GetHubContext<T>() yourself to find the IHubContext for your hub type.
So, you can call your StatusController::Post method with any of the standard .NET HTTP APIs: HttpClient, WebClient or HttpWebRequest.

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

SignalR - Set ClientID Manually

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?

Resources