SignalR Routing Hub

Viewed 75

I am trying to add SignalR to an existing .NET 6 web application, following the standard guide produced by Microsoft

The use case I am trying to accomplish is this. Its a sports statistics fucntion whereby a timer is started on a mobile device, and the timer runs on a remote device (TV) and this differs on a match by match basis.

So a user on a mobile phone is on a page in the web app at /scorecard/{matchid}/. On a TV screen (or many really, any other listeners) is another webpage running on /bigscreen/{matchid}. When the user presses a button on the mobile phone, a message is sent to the page running on the TV.

The code below works as a static hub, however I am modifying it to try and use similar routes, the {id} value will be different depending on the match being used.

How can I make the existing SignalR hub to be a bit more dynamic based upon the matchid value?

Web Page Controller

[HttpGet("/signalrtest/test/{matchid}")]
public async Task<IActionResult> Index(int matchid)
{        
    ScoreboardViewModel vm = new ScoreboardViewModel();
    vm.MatchId = matchid;
    return View(vm);
}

Web Page Razor

@model ScoreboardViewModel
@{
    ViewData["Title"] = "SignalR Test";
    Layout = "~/Views/Shared/_LayoutLogin.cshtml";
}
<div class="container">
        <div class="row">&nbsp;</div>
        <div class="row">
            <div class="col-2">User</div>
            <div class="col-4"><input type="text" id="userInput" /></div>
        </div>
        <div class="row">
            <div class="col-2">Message</div>
            <div class="col-4"><input type="text" id="messageInput" /></div>
        </div>
        <div class="row">&nbsp;</div>
        <div class="row">
            <div class="col-6">
                <input type="button" id="sendButton" value="Send Message" />
            </div>
        </div>
    </div>
    <div class="row">
        <div class="col-12">
            <hr />
        </div>
    </div>
    <div class="row">
        <div class="col-6">
            <ul id="messagesList"></ul>
        </div>
    </div>
<script src="~/js/signalr.js"></script>
<script type="text/javascript">
    "use strict";

    var connection = new signalR.HubConnectionBuilder().withUrl("/signalrtesthub/@Model.MatchId").build();

    //Disable the send button until connection is established.
    document.getElementById("sendButton").disabled = true;

    connection.on("ReceiveMessage", function (user, message) {
        var li = document.createElement("li");
        document.getElementById("messagesList").appendChild(li);
        // We can assign user-supplied strings to an element's textContent because it
        // is not interpreted as markup. If you're assigning in any other way, you 
        // should be aware of possible script injection concerns.
        li.textContent = `${user} says ${message}`;
    });

    connection.start().then(function () {
        document.getElementById("sendButton").disabled = false;
    }).catch(function (err) {
        return console.error(err.toString());
    });

    document.getElementById("sendButton").addEventListener("click", function (event) {
        var user = document.getElementById("userInput").value;
        var message = document.getElementById("messageInput").value;
        connection.invoke("SendMessage", user, message).catch(function (err) {
            return console.error(err.toString());
        });
        event.preventDefault();
    });
</script>

Signal R Hub Class

public class SignalRHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

Startup.cs

app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<SignalRHub>("/signalrtesthub/{matchid}");
});
1 Answers

A possible workaround for this scenario would be:

First create a method to SignalRHub class like this

public Task SendMessageToGroup(string group, string user, string message)
{
    return Clients.OthersInGroup(group).SendAsync("ReceiveMessageOnGroup", user, message);
}

the group in your case is the {matchId}.

Second in your controller inject an IHubContext<SignalRHub>, This will notify your clients every time this endpoint is hit. For example you can add it here (or maybe into an API controller):

[HttpGet("/signalrtest/test/{matchid}")]
public async Task<IActionResult> Index(int matchid)
{        
    ScoreboardViewModel vm = new ScoreboardViewModel();
    vm.MatchId = matchid;
    var json = JsonSerializer.Serialize(data);
    await _hubContext.Clients.Group(matchid).SendAsync("ReceiveMessageOnGroup", json);
    return View(vm);
}

Last but not least you must add each client to the group, so on client side initialize connection like:

private async Task InitializeHubConnectionAsync()
{
    _hubConnection.On<string>("ReceiveMessageOnGroup", HandleMessageAsync);
    await _hubConnection.StartAsync();
    await _hubConnection.InvokeAsync("JoinGroup", matchId);
}
private async Task HandleMessageAsync(string message) 
{
   //Your logic
}

and on server side add this method as well to your hub:

public Task JoinGroup(string group)
{
    return Groups.AddToGroupAsync(Context.ConnectionId, group);
}
Related