How to consume a web service in the server from a hosted blazor webassembly app with Signal-R (.NET Core 6.0)

453 Views Asked by At

I have written a hosted blazor web assembly app with SignalR and I need to figure out how to consume a web service in the server side.

An API controller on the server side

The idea is that this service has to be consumed by a third party application which will pass some information and it will trigger the Hub's Send function, which in turn should update a specific client (identified by the connectionId) with the information sent by the third party app.

Here is the controller's code (I wrote it following several examples and some answers here)

[Route("api/[controller]/[action]")]
[ApiController]
public class TicketsController : ControllerBase
{
    private readonly IHubContext<TicketHub> _hubContext;

    public TicketsController(IHubContext<TicketHub> hubContext)
    {
        _hubContext = hubContext;
    }

    [HttpGet]
    public async Task setTicket(string ticketText, string connectionId)
    {
        await _hubContext.Clients.All.SendAsync(ticketText, connectionId);
    }
}

Here is the Hub class' code

public class TicketHub : Hub
{
    public async Task Send(string lastCalledTicket, string connectionId)
    {
        await Clients.Client(connectionId).SendAsync("GetLastcalledTicket", lastCalledTicket);
    }

    public string GetConnectionId() => Context.ConnectionId;
}

I also added the hub mapping in the Program.cs file

app.MapHub<TicketHub>("/LastCalledTicket");

I ran the application from the server app and then in the browser I typed something like:

https://localhost:somePortNumber/LastCalledTicket/someTextHere/connectionId

And nothing happened, it just opened the client side app again.

Some sort of diagram of what I'm trying to do

Is this possible? (I'd assume it is)

What am I missing in order to achieve this this?

Thank in advance for your help.

2

There are 2 best solutions below

0
On

So, after looking around I finally found a tutorial that I could adapt to what I needed (the tutorial uses Angular for the front end). To make it short: I have to rewrite the API to include SignalR and modify the front to start the hub connection pointing to said API.

Here's a diagram on how the structure changed

Following the tutorial I created this Interface:

public interface IMessageHubClient
{
    Task SendTicketToUser(string ticket);
}

A class using SignalR:

public class MessageHub : Hub <IMessageHubClient>
{
    public async Task SendTicketToUser(string ticket)
    {
        await Clients.All.SendTicketToUser(ticket);
    }
}

And the API controller:

[Route("api/[controller]/[action]")]
[ApiController]
public class TicketsController : ControllerBase
{

    private IHubContext<MessageHub, IMessageHubClient> messageHub;

    public TicketsController(IHubContext<MessageHub, IMessageHubClient> _messageHub)
    {
        messageHub = _messageHub;
    }

    [HttpGet("{ticketInfo}/{connectionId}")]
    public string SendTicketInfo(string ticketInfo, string connectionId)
    {
        messageHub.Clients.Client(connectionId).SendTicketToUser(ticketInfo);
        return "Ticket enviado";
    }
}

Added the Endpoints to Program.cs:

app.UseEndpoints(endpoints => {
    endpoints.MapControllers();
    endpoints.MapHub<MessageHub>("/tickets");
});

Something I came to realize is that I was passing the connectionId to the Send method when it actually had to be passed to the API.

In a separate project for the front app, in the razor component I had to point the HubConnectionBuilder to the API's URL that was mapped in the endpoints section in Program.cs: My component code now looks like this:

@page "/"
@using Microsoft.AspNetCore.SignalR.Client
@implements IAsyncDisposable
@inject NavigationManager _navigationManager
<PageTitle>Index</PageTitle>

<h1>@ticketText</h1>


@code{
    private HubConnection hubConnection;
    public string ticketText;
    public string connId;
    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
        .WithUrl("https://localhost:7288/tickets")
        .Build();

        hubConnection.On<string>("SendTicketToUser", (ticketInfo) =>
        {

            ticketText = ticketInfo;

            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }

    async Task Send() => await hubConnection.SendAsync("SendTicketToUser", ticketText);

    public async ValueTask DisposeAsync()
    {
        await hubConnection.DisposeAsync();
    }
}

For testing purposes I can just hard code the API's URL, but when the time comes I can add an appsettings.json file to the front app and store the API's URL there (in case it ever changes). Now it works, and when the API method is consumed it updates the front end's text with whatever I'm passing in the ticketInfo parameter as long as the connectionId matches the connected client's connectionId.

Thanks to all for the help.

0
On

You are calling your own hub with the hubcontext call the client directly.

[HttpGet]
public async Task setTicket(string ticketText, string connectionId)
{
    await _hubContext.Clients.Client(connectionId).SendAsync("GetLastcalledTicket", lastCalledTicket)
}