9

I need to send an instant message from the server to the client after the user has submitted a form in a browser.

I followed the Microsoft steps here to set up a signalR connection, created a Hub class, signalr.js etc.

The problem is that I can only invoke a message to all clients, but I need to invoke the message to the specific caller who initiated the request (otherwise everyone will get the message).

This is my POST Action in the HomeController.cs:

[HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
    {
        //Invoke signal to all clients sending a message to initSignal WORKS FINE
        await _signalHubContext.Clients.All.SendAsync("initSignal", "This is a message from the server!");

        //Invoke signal to specified client where signalRConnectionId = connection.id DOES NOT WORK
        await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);

        return RedirectToAction("Success", inputs);
    }

my client javascript file:

    //Create connection and start it
const connection = new signalR.HubConnectionBuilder()
    .withUrl("/signalHub")
    .configureLogging(signalR.LogLevel.Information)
    .build();
connection.start().catch(err => console.error(err.toString()));

console.log("connectionID: " + connection.id);
$("#signalRconnectionId").attr("value", connection.id);

//Signal method invoked from server
connection.on("initSignal", (message) => {

    console.log("We got signal! and the message is: " + message);


});

I have tried debugging the action method and I get correctly passed in the connectionId which is "0" (incrementing by 1 per connection)

Morten_564834
  • 1,479
  • 3
  • 15
  • 26
  • On the page you linked, there is another link that explains `Clients` : https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.1 – Neil Aug 13 '18 at 11:26
  • `Clients.Caller.SendAsync("ReceiveMessage", message);` - This will invoke the client side function only for the client who initiated the request. – Thangadurai Aug 13 '18 at 13:16
  • Have you tried it yourself? When I use _signalHubContext.Clients there is no caller method available. If I do it from the Hub class there is, but then I need to put business logic in there, I would like to do it from the hubcontext I got through DI in the controller – Morten_564834 Aug 13 '18 at 16:25
  • Where is this assumption from: `connectionId which is "0" (incrementing by 1 per connection)` – aaron Aug 15 '18 at 06:00
  • Possible duplicate of [Connection ID when calling SignalR Core Hub method from Controller](https://stackoverflow.com/questions/50367586/connection-id-when-calling-signalr-core-hub-method-from-controller) – aaron Aug 15 '18 at 06:00
  • @aaron you're right this is just my assumption based on my own observations; if I start connection the first time it has Id of 0, second time it has 1 and so forth. But I can't find any documentation on what the expected Id should look like and how it works. – Morten_564834 Aug 16 '18 at 13:59
  • the context.Id is not the right Id to get, the actual ConnectionId looks like this: at4Hldd4GnWiSaO1Cit-mQ – Morten_564834 Aug 24 '18 at 12:45

2 Answers2

8

So here's the final solution I came up with inspired by the answer from this thread

I got the connectionId by calling the Hub class from client and then from client calling the controller passing in the connectionId.

Hub class:

public class SignalHub : Hub
{
    

    public string GetConnectionId()
    {
        return Context.ConnectionId;
    }
}

client-side javascript code executed at startup:

connection.start().catch(err => console.error(err.toString())).then(function(){
connection.invoke('getConnectionId')
    .then(function (connectionId) {
        // Send the connectionId to controller
        console.log("connectionID: " + connectionId);
        $("#signalRconnectionId").attr("value", connectionId);
    });
});

HomeController.cs:

public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
    {
        
        //Invoke signal to specified client WORKS NOW
        await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);

        return RedirectToAction("Success", inputs);
    }

It works fine, but still feels a little like a roundtrip, it would have been easier if we didn't have to go through the hub class to make this happen. Maybe just having the connectionId from the client-side to begin with, but maybe there is a good reason for the design :)

Avshalom
  • 8,657
  • 1
  • 25
  • 43
Morten_564834
  • 1,479
  • 3
  • 15
  • 26
  • I was not able to invoke the hub method, where I have business logic within the method too. But it seems like it was only sending the message directly to the front end user. Oh, and I was able to user _hubContext.Clients.User(userId).SendAsync("method1", message) – Alan Chang Jun 19 '19 at 01:49
0

According to Microsoft, you can not access to the ConnectionId and Caller from outside a hub https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1

When hub methods are called from outside of the Hub class, there's no caller associated with the invocation. Therefore, there's no access to the ConnectionId, Caller, and Others properties.

user8013875
  • 92
  • 1
  • 1
  • 9
  • Yes thanks - bummer :/. Wonder if this is a technical boundary or why they would do this, it kind of ruins the flexibility of instant communication from server. I would then need to call a Hub method from client when form is submitted and then inside this method call a controller action or another repository method, hence mixing business logic within a hub class hmm – Morten_564834 Aug 16 '18 at 08:03
  • Just use the UserId instead – davidfowl Aug 17 '18 at 04:34
  • 1
    But UserId would require the site to use authentication right? – Morten_564834 Aug 17 '18 at 13:26