push data from server to client with specified Id signalR - signalr

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.

Related

SignalR client specific with angularjs

So I am fairly new with signalR and had worked with it a bit with MVCs. Now I am using it in webapi with angularjs and am a bit confused or have forgotten of what I have done. I am using bearer tokens with webapi and am trying to create a notification system.
What I want to figure out is the proper way of using angularjs with signalR. I see many people use the proxy on/invoke. Is the proxy.on is when I call the hubcontext from the server as so:
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
hubContext.Clients.User(UserId).broadcastNotification("Good morning! The time is " + DateTime.Now.ToString());
and the proxy.invoke method is from the client side? If so, which would be the best way for using notification systems (I would assume the proxy.on)?
My second question is more on sending notifications to specific users. For sending requests to specific users, I would assume you would want to do this on the hub as so:
public void SendNotification(string userId)
{
Clients.User(userId).broadcastNotification("Good morning! The time is " + DateTime.Now.ToString());
}
My startup is something like this:
public class MyProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
var userId = request.User.Identity.Name;
return userId.ToString();
}
}
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
Database.SetInitializer(new MigrateDatabaseToLatestVersion<AuthContext, Travelfy.API.Migrations.Configuration>());
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyProvider());
app.MapSignalR("/hubs", new HubConfiguration());
}
When I refresh my pages, I notice that all my userids are all empty strings "". I was reading that maybe it was due to using bearer tokens. If so, how would I use bearer tokens to specific the userId that I would want to send to? When I use the Clients.All everything works fine, so I'm assuming it has to be something with the startup/userIds I am getting.
Thanks
To answer your first question:
Which would be the best way for using notification systems
If you want to push notifications from the server towards the client, you have to define a new handler is to define a method on the client (with the generated proxy) like this:
How to define methods on the client that the server can call
If you want the client to call a method that lies on the server, you have to use this method:
How to call server methods from the client
To answer your second question:
For sending requests to specific users, I would assume you would want
to do this on the hub
You could use the connection ID of the client you wish to target. See this:
Calling client methods
So after a while, I was able to figure out the right answer. Because I was using bearerTokens, I really had to determine another method of obtaining the userId rather than just relying on request.User.Identity.Name. What I needed to do was pass my bearerToken to the connection.qs value.
connection.qs = { Bearer: token };
Once I was able to do that I had to route the find my user based on the token that I had sent in.
var token = request.QueryString.Get("Bearer");
var authenticationTicket = Startup.OAuthBearerOptions.AccessTokenFormat.Unprotect(token);

SignalR - sending parameter to OnConnected?

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

SignalR Adding/Removing Connections from a dictionary and finding Group values from dictionary

I have Organizations that login to an asp.net website and when each member logs in I add their ConnectionId and OrganizationId to a static ConcurrentDictionary named OrganizationMembers in a SignalR Hub OnConnected override. I do this so I can send data to a page callback function that contains data relative only to that Organization (SignalR Group). The OrganizationId will represent the SignalR Group that I send data to.
Also in the OnConnnected method, I add the OrganizationId and OrganizationId.ToString() to another ConcurrentDictionary that represents unique Organization Ids. I'll explain later why I store the OrganizationId.ToString(). I store unique Organization Ids so that in a background Task that calls a method and sleeps over and over, I can enumerate the unique Organization Ids and only send each Organization (SignalR Group) data relevant to it.
In the OnDisconnected Hub override, after I remove a Connection, I want to check the OrganizationId values in the OrganizationMembers ConcurrentDictionary to see if that was the last Member with that OrganizationId to disconnect and if so remove it from the UniqueOrganizations dictionary. I know the dictionary Values.Contains() is O(n) so I'd really like to avoid this.
This is so when the task enumerates the UniqueOrganizations, there won't be Organizations (SignalR Groups) that I'm attempting to send data to needlessly in the case that say for example 5 Members from the same Organization log in but all later close their browser, I won't be attempting to send that Group data via the SignalR callback.
Admittedly I don't know the internal workings of the SignalR Hub so it may be the case that it won't matter if I try to send data needlessly to Members of Organizations (SignalR Groups) that have all disconnected.
Is this over-thinking the SignalR Hub? Should I not worry about determining if the last Organization (SignalR Group) Member has disconnected from the Hub and not remove the OrganizationId from the UniqueOrganizations?
If this is OK to do, how can I avoid the dictionary Values.Contains() since it's O(n)?
// the key is the ConnectionId of an Organization Member
// the value is the OrganizationId of the Member
protected static ConcurrentDictionary<string, int> _organizationMembers;
public static ConcurrentDictionary<string, int> OrganizationMembers {
get {
if(_organizationMembers == null) {
_organizationMembers = new ConcurrentDictionary<string, int>();
}
return _organizationMembers;
}
}
// the key is the OrganizationId to send the specific data to
// the value is the OrganizationId converted to string
protected static ConcurrentDictionary<int, string> _uniqueOrganizations;
public static ConcurrentDictionary<int, string> UniqueOrganizations {
get {
if(_uniqueOrganizations == null) {
_uniqueOrganizations = new ConcurrentDictionary<int, string>();
}
return _uniqueOrganizations;
}
}
// Hub Code
public override Task OnConnected() {
string connectionId = Context.ConnectionId;
string organizationId = Context.Request.QueryString["organizationId"];
int organizationIdValue = int.Parse(organizationId);
OrganizationMembers.TryAdd(connectionId, organizationIdValue);
UniqueOrganizations.TryAdd(organizationIdValue, organizationId);
// the organizationId represents the SignalR group
Groups.Add(connectionId, organizationId);
return base.OnConnected();
}
public override Task OnDisconnected() {
string organizationId = string.Empty;
int organizationIdValue;
string connectionId = Context.ConnectionId;
OrganizationMembers.TryRemove(connectionId, out organizationIdValue);
// if that happens to be the last Member connection to be removed
// then remove the OrganizationId from the unique OrganizationIds
// so it won't be a wasted enumeration and useless callback
// I want to avoid this O(n) Contains()
if(!OrganizationMembers.Values.Contains(organizationIdValue)) {
UniqueOrganizations.TryRemove(organizationIdValue, out organizationId);
}
Groups.Remove(connectionId, organizationId);
return base.OnDisconnected();
}
// Task code
foreach(int organizationIdValue in DataCache.UniqueOrganizations.Keys) {
// this is why I also stored the OrganizationId as a string so I wouldn't have to
// convert it to a string each time the dictionary is enumerated.
// I can just have the organizationId string ready to use as the SignalR Group.
string organizationId = UniqueOrganizations[organizationIdValue];
try {
string organizationData = GetOrganizationData(organizationIdValue);
_clients.Group(organizationId).sendToOrganizationData(organizationData);
}
catch(Exception ex) {
_clients.Group(organizationId).sendToOrganizationError();
}
}
First of all, SignalR is pretty smart about not wasting resources when sending to groups without any subscriptions, so you should be fine sending to groups without any members as long as it's OK to waste a few cycles doing that.
If you don't have too many organizations you can have a ConcurrentDictionary<int,int> with all your organization ids as your keys and the number of connected members as your value. In OnConnected and OnDisconnected in could use Interlocked.Increment and Interlocked.Decrement respectively to keep track of the currently connected members. Then in your task could loop over the keys and skip any organization with zero connected members.
This new ConcurrentDictionary could replace _uniqueOrganizations if you don't mind calling key.ToString(CultureInfo.InvariantCulture) to get the group name.

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?

SignalR(Hub) can send a message except signal maker?

I'm studying SingalR(https://github.com/SignalR/SignalR).
I'm really want to send a message to all connection except the person who makes a event.
For example,
In Chatting application, there is three client(A,B,C).
Client A type a message, "Hello" and clikc submit.
Clients.addMessage(data); send "Hello" to All Cleint(include cleint A)
I want to send "Hello" only Client B and C.
How can I achieve it?
// I think this can get all Clients, right?
var clients = Hub.GetClients<Chat>();
There's no way to filter message on the server today, but you can block messages to the caller from the client side. If you look at some of the samples on signalr, you'll see that they assign each client a generated id to the client in a method (usually called join). Whenever you invoke a method from the hub, pass the id of the calling client, then on the client side do a check to make sure the id of the client isn't the same as the caller. e.g.
public class Chat : Hub {
public void Join() {
// Assign the caller and id
Caller.id = Guid.NewGuid().ToString();
}
public void DoSomething() {
// Pass the caller's id back to the client along with any extra data
Clients.doIt(Caller.id, "value");
}
}
Client side
var chat = $.connection.chat;
chat.doIt = function(id, value) {
if(chat.id === id) {
// The id is the same so do nothing
return;
}
// Otherwise do it!
alert(value);
};
Hope that helps.
It is now (v1.0.0) possible by using Clients.Others property in your Hub.
E.g.: Clients.Others.addMessage(data) calls the method addMessage on all clients except the caller.

Resources