3

I am trying to call a method in the signalr Hub class from an (ASP.NET Core) MVC Controller, but I cannot find an example online that shows how to.

Note: There are lots of examples using older versions of signalr with the .Net Framework, but none that I can see that show how to do this in .Net Core.

I need to pass an id from the an MVC Action Result directly through to my Hub, without passing the id to the page, and then having to get a client connection back through to the hub.

public class ChatHub : Hub
{ 
    public async Task DoSomething(int id)
    {            
        //// Something in here.
    }
}



public class HomeController : Controller
{
    private readonly IHubContext<ChatHub> _hubContext;

    public HomeController(IHubContext<ChatHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task<ActionResult> Index(int id) 
    {

         //// Call the DoSomething method from here, passing the id across.
         await _hubContext.Clients.All.SendAsync("AddToGroup", groupId);
    }
}

Is there a way to do this please? (Or am I looking at this the wrong way, and is there a better way to achieve the same result?)

Update: If I pass the Id into the view, and then use JavaScript to call the Hub, this then calls the DoSomething method, so I can see it all hangs together correctly, but not when I try to call it in C#.

Brett Rigby
  • 6,101
  • 10
  • 46
  • 76
  • inject the hub into the controller as a dependency and call the desired member – Nkosi Oct 09 '18 at 09:29
  • Hi @Nkosi - I've updated the post to show this, as this is actually the code I've been playing around with, but still isn't able to call the method, – Brett Rigby Oct 09 '18 at 10:13
  • the context allows you to do the same thing you would have done in the `DoSomething` method. What does the method do? – Nkosi Oct 09 '18 at 10:22
  • @Nkosi - So, _hubContext.Clients.All.SendAsync_ calls the *client* (javascript) code, hitting the 'AddToGroup' method in there? I see... – Brett Rigby Oct 09 '18 at 12:17
  • Possible duplicate of [Call SignalR Core Hub method from Controller](https://stackoverflow.com/questions/46904678/call-signalr-core-hub-method-from-controller) – Khushali Nov 30 '18 at 10:20
  • @Khushali - Good point, but I had previously read that question and feel it didn't answer the question, hence my posting this one above. – Brett Rigby Dec 13 '18 at 16:04

3 Answers3

5

I think you're misunderstanding how it all works together (which is the same thing I did up until yesterday), the hub code is for the client-side script code to call back into and then action, whereas the IHubContext is used as the strongly typed methods that will be sent to the Client-side

Hub

// This class is used by the JavaScript Client to call into the .net core application.
public class ChatHub : Hub<IChatClient>
{

    public static ConcurrentDictionary<string, string> Connections = new ConcurrentDictionary<string, string>();

    // As an example, On connection save the user name with a link to the client Id for later user callback
    public override Task OnConnectedAsync()
    {
        var user = Context.User.Identity.Name;

        Connections.AddOrUpdate(this.Context.ConnectionId, user, (key, oldValue) => user);

        return base.OnConnectedAsync();
    }

    public override Task OnDisconnectedAsync(Exception exception)
    {
        // Do something on disconnect.
    }

    // Add other methods you want to be able to call from JavaScript side in here...
    public void SendMessage(int id, string message)
    {
        // Message doing stuff here.
    }
}

ChatClient Interface

// This provides strongly-typed methods that you'll have on the Client side but these don't exist on the server.
public interface IChatClient
{
    //So this method is a JS one not a .net one and will be called on the client(s)
    Task DoSomething(int id);

    Task NotificationUpdate(int id, string message);
}

Controller

public class HomeController : Controller
{
    private readonly IHubContext<ChatHub, IChatClient> _hubContext;

    public HomeController(IHubContext<ChatHub, IChatClient> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task<ActionResult> Index(int id) 
    {

         // This calls the method on the Client-side
         await _hubContext.Clients.All.DoSomething(id);
    }
}
Stephen
  • 1,737
  • 2
  • 26
  • 37
  • Could you share the JS client side? Is it something like `connection.on("DoSomething", function(message) { console.log("something received : " + message); });` For some reason, nothing happens on the client side. Thanks – Yahia Dec 13 '18 at 14:54
  • Actually the client now receives the data, the pb was that route.MapHub was **not** using the strongly typed hub – Yahia Dec 13 '18 at 15:51
  • @Stephen Hello! can you share the js client side code for this? – Mary Nov 09 '22 at 18:17
  • Update: I was able to make it work, but I am using Client.User – Mary Nov 09 '22 at 19:03
2

You can use the IHubContext to do this:

public class HomeController : Controller
{
    private readonly IHubContext<ChatHub> _hubContext;

    public HomeController(IHubContext<ChatHub> hubContext)
    {
        _hubContext = hubContext;
    }

    public async Task<ActionResult> Index(int id) 
    {
         //// Call the DoSomething method from here, passing the id across.
         await _hubContext.Clients.All.SendAsync("DoSomething", id);
    }
}

Full docs and examples here.

marcusturewicz
  • 2,394
  • 2
  • 23
  • 38
  • Thanks @tura08, but this doesn't work, I'm afraid. The 'Send' method no longer exists, and is instead replaced with SendAsync and SendCoreAsync, but these don't seem to actually hit my DoSomething method in the hub. :-( – Brett Rigby Oct 09 '18 at 10:02
  • 1
    Ahh yes, I just put Send because your method was synchronous. If you change it to async and await it, that should work. Otherwise there must be some other problem. – marcusturewicz Oct 09 '18 at 10:22
  • Indeed - have updated the original post to use async/await. :o) – Brett Rigby Oct 09 '18 at 10:46
  • 1
    This is calling directly to your clients, so you should not expect it to hit your `DoSomething` method in your hub (server), but rather your `DoSomething` receiving code in your client side code. Are you receiving the message in the client, because that is what you're really trying to achieve here... – marcusturewicz Oct 09 '18 at 11:13
  • Hmm - I think it *might do*, except if this is running in the controller, has the view even been built yet? Surely the view is the output of the controller, which hasn't yet finished, and that the signalr connection hasn't yet been made. No? – Brett Rigby Oct 09 '18 at 12:12
  • It depends. You need to pay attention to the asynchronous nature of how this works. When you call out to the hub, all clients registered at that exact moment will get the message. At some point, each client would have had to receive an HTML document with the SignalR client-side code and opened a connection to the hub, yes. If the view being returned is what does that, then the current client making the request will not get the message, but other clients that have already subscribed to it will. – Chris Pratt Oct 09 '18 at 12:56
  • @ChrisPratt - Very true - have to remember about the other connected clients! – Brett Rigby Oct 09 '18 at 19:32
0

I used the code here https://stackoverflow.com/a/53062957/6453193 for my HUB connection.

Since the js client-side code was not posted here. Just wanna share how I do it in js.

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/chatHub")
    .configureLogging(signalR.LogLevel.Information)
    .withAutomaticReconnect()
    .build();

async function start() {
    try {
        await connection.start();
        console.log("SignalR Connected.");
    } catch (err) {
        console.log(err);
        setTimeout(start, 5000);
    }
};

connection.onclose(async () => {
    await start();
});

// Trigger if server-side code call it using NotificationUpdate
connection.on("NotificationUpdate", (message) => {
    alert(message);
});

// Start the connection.
start();

and my controller notifying the client-side

 await _hubContext.Clients.User(User.Identity.Name).NotificationUpdate($"This is an update");
Mary
  • 564
  • 3
  • 14