Teresa Alves
Teresa Alves

Reputation: 523

How can I call my hub on my controller (SignalR)?

Everytime something is posted and then added to the database, I want it to be shown on my Index.cshtml. Right now I'm doing like represented below. However this option is not working:

 [HttpPost]
    public async Task<IActionResult> PostIgnicoes([FromBody] Ignicoes ignicao)
    {
        **var hub = new MyHub();**
        if (ignicao.Latitude == null)
       {
        return BadRequest(ModelState);
    }
    else
    {
        if (ignicao.Longitude == null)
        {
            return BadRequest(ModelState);
        }
        else
        {
            //if(ignicao.Estado == null)
            //{
            //    return BadRequest(ModelState);
            //}

        }
    }

    _context.Ignicoes.Add(ignicao);


    **await hub.PostMarker();**
    await _context.SaveChangesAsync();


    return CreatedAtAction("GetIgnicoes", new { id = ignicao.Id }, ignicao);
}

But everytime I post something it return an error 500, which mean I can't post anything. Why does this happen?

Upvotes: 0

Views: 1041

Answers (2)

Tieson T.
Tieson T.

Reputation: 21239

This is how I understood SignalR to work. There may be more to it, but building something similar to what I explain below enabled me to build real-time notifications into the application I'm building at the moment.

Hub

The Hub doesn't need to have any methods defined on it in order to enable server-sent events. It's mainly a hook for the other SignalR components that you wire up. So, let's create a mostly empty Hub, which I'll call MapHub:

using Microsoft.AspNetCore.SignalR;

namespace YourApplicationNamespace
{
    public class MapHub : Hub
    {
    }
}

That's it. Seems odd, but let's continue.

IHubContext<T>

This is actually what you would use to send a notification to registered clients. You would register this in your Startup (or wherever your DI container is configured). This is done for you when you use something similar to:

app.UseEndpoints(endpoints => {
    endpoints.MapHub<MapHub>("/notify"); 
});

"/notify" is the endpoint I'm going to use for this hub. It's what SignalR will use to register clients.

Then, update your controller, and add IHubContext<MapHub> as a dependency:

public class MapsController : Controller
{
    private readonly IHubContext<MapHub> hubContext;

    public MapsController(IHubContext<MapHub> hubContext)
    {
        this.hubContext = hubContext ?? throw new ArgumentNullException(nameof(hubContext));
    }

    // ... the rest of your code

}

Once that's done, you'll modify your action.

Notify clients to get data

Rather than call

**await hub.PostMarker();**

you'll do something like this:

[HttpPost]
public async Task<IActionResult> PostIgnicoes([FromBody] Ignicoes ignicao)
{
    if (ignicao.Latitude == null)
    {
        return BadRequest(ModelState);
    }
    else
    {
        if (ignicao.Longitude == null)
        {
            return BadRequest(ModelState);
        }
    }

    _context.Ignicoes.Add(ignicao);

    await _context.SaveChangesAsync();

    // THIS IS THE NEW SIGNALR CODE
    await hubContext.Clients.All.SendAsync("getMarkers");

    return CreatedAtAction("GetIgnicoes", new { id = ignicao.Id }, ignicao);
}

Notice that you don't directly use the Hub - that class is primarily for the bits that get wired up for you via the .MapHub extension in Startup and the JavaScript code we haven't talked about yet.

Clients.All.SendAsync("getMarkers") tells SignalR to send a notification to all registered clients and triggers the event getMarkers (defined below).

Client Setup

I'm going to assume you already have the SignalR client scripts downloaded and loaded on the page. The code below is how I setup my connection:

try {
    const connection = new signalR.HubConnectionBuilder()
        .withUrl('/notify')
        .withAutomaticReconnect()
        .configureLogging(signalR.LogLevel.Warning)
        .build();

    connection.on('getMarkers', function () {
        // ADD CODE HERE TO REQUEST NEW MARKERS
    });

    connection.start().then(function () {
        console.log("Hub connected");
    });
}
catch (err) {
    console.error(err);
}

That's more or less cookie-cutter from the SignalR tutorials on Microsoft Docs. The key part is

    connection.on('getMarkers', function () {
        // ADD CODE HERE
    });

That's how you setup the "listening" endpoint for your clients, which is the endpoint called by hubContext.Clients.All.SendAsync("getMarkers") (in my example). What you do at // ADD CODE HERE is up to you. My instance uses jQuery's $.load function to reload a sidebar on my page.

Upvotes: 1

poke
poke

Reputation: 388303

In SignalR, a Hub is basically the collection of “methods” that a client can call. So this is the interface the client works with, just like a controller would work for a web API.

If you want to call methods on the clients, then using the hub is not the right approach. Instead, you want to use the IHubContext. You will need to inject that into your controller.

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

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

    [HttpPost]
    public async Task<IActionResult> PostIgnicoes([FromBody] Ignicoes ignicao)
    {
        //…

        await _hubContext.Clients.All.SendAsync("PostMarker");
        await _context.SaveChangesAsync();

        //…
    }
}

For more details, check out the documentation on sending messages from outside a hub. You can also this with use strongly typed hubs to avoid having to call untyped methods by passing their name as a string.

Upvotes: 1

Related Questions