SignalR Groups: Do not send message back to sender - signalr

When I send a message to a signalR group that I am subscribed to, I get the message back!
I do not want this, I want it to send the message to everyone else in the group.
Is this possible? How?

Now with SignalR you are able to use
Clients.OthersInGroup("foo").send(message);
which does exactly what you're after. It will send a SignalR client message to everyone in a group except the caller.
You can read more here: SignalR wiki Hubs

What you can do here is that you can send the ConnectionId to the client and inspect that. For example, the below one is your hub:
[HubName("moveShape")]
public class MoveShapeHub : Hub
{
public void MoveShape(double x, double y)
{
Clients.shapeMoved(Context.ConnectionId, x, y);
}
}
At the client level, you can do the following:
var hub = $.connection.moveShape,
$shape = $("#shape"),
$clientCount = $("#clientCount"),
body = window.document.body;
$.extend(hub, {
shapeMoved: function (cid, x, y) {
if ($.connection.hub.id !== cid) {
$shape.css({
left: (body.clientWidth - $shape.width()) * x,
top: (body.clientHeight - $shape.height()) * y
});
}
}
});
Edit
Starting from SignalR 1.0.0-alpha, there is a built-in API for that if you are using Hubs:
[HubName("moveShape")]
public class MoveShapeHub : Hub
{
public void MoveShape(double x, double y)
{
Clients.Others.shapeMoved(x, y);
}
}
This will broadcast the data everybody but the Caller.

Related

How to make a Blazor page update the content of one html tag with incoming data from gRPC service

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/

Are SignalR connectionIDs hub-specific?

If I have several hubs, and connect a single JavaScript client to all of them, will the context ConnectionID be the same between them?
Interesting question. I didn't know the answer, so I tested it using this example by changing it a bit.
The Hub classes:
public class ChatHub : Hub {
public void Send(string name, string message) {
string cid = Context.ConnectionId;
Clients.All.sendMessage(name, message);
}
}
public class ChatHub2 : Hub
{
public void Send(string name, string message)
{
string cid = Context.ConnectionId;
Clients.All.sendMessage(name, message);
}
}
The page.html connecting to the hubs:
var chat = $.connection.chatHub;
var chat2 = $.connection.chatHub2;
$.connection.hub.start().done(function () {
// Call the Send method on the hub.
chat.server.send('Me', 'Message to 1');
chat2.server.send('Me', 'Message to 2');
});
I set breakpoints on the Hub methods and both are called, and Context.ConnectionId are the same. That's what I was expecting. Give it a try!
It makes sense, it supposed to use the same connection to send the message over.

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.

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(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