A taste of SignalR Core

What is SignalR?

The beginnings

ASP.Net (both “bare” Web Forms and ASP.Net MVC introduced later) is developed around a traditional HTTP request and response mechanism –
the server provides content in form of HTML pages or files only when they are being requested by client. In contrast, SignalR provides two-way communication between server and client – both can trigger actions in each other and the result can be shown immediately in a browser without the need for refreshing.
The last part assumes that there is a browser involved – which doesn’t necessarily has to be true, as apart from javascript implementation of SignalR, a C# one (with more already announced) exists – SignalR isn’t constrained to just web pages.

One of the main selling points of SignalR was its simplicity of use. With just a few lines of code, it was easy to set up a simple chat. (And not only that – one of the examples provided by Microsoft was a browser multiplayer shooter game).

Here comes .Net Core

The drawbacks emerging from design choices soon became apparent (e.g. some problematic features making some aspects of SignalR difficulty to build, dependence on jQuery), especially since the release of .Net Core with which SignalR was incompatible. A decision was made that the next version of SignalR will introduce some major changes in order to be more suited to development with .Net Core.
As of now, .Net Core has already reached its 2.0 version, and SignalR Core is still in development (alpha version is available since September 2017).

SignalR basics

In this section, we will make a usable web application based on ASP.Net MVC with SignalR Core configured and running.

First, make a new “ASP.NET Core Web Application” project (use “Web Application Model-View-Controller” template). Then add Microsoft.AspNetCore.SignalR.Core using NuGet package manager (currently, the prerelease version marked “v1.0.0-alpha2-final” can be found there – one can also add https://dotnet.myget.org/F/aspnetcore-ci-dev/api/v3/index.json NuGet server and get the latest build from there).

The simplest use case of SignalR is using so-called hubs (which is an abstraction built upon lower-level PersistentConnection class). We can make a class inheriting from Hub

namespace SignalRTest.Hubs
{
    public class TestHub : Hub
    {
        public Task Send(string message)
        {
            return Clients.All.InvokeAsync("Send", message);
        }
    }
}

and add a route, allowing instances of our class to both send and receive messages between server and clients:

namespace SignalRTest
{
    public class Startup
    {
        // (...)

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
            services.AddSignalR();
        }
 
       
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            // (...)

            app.UseSignalR(routes =>
            {
                routes.MapHub<TestHub>("testHub");
            });
        }
    }
}

Now, each time a client will try to connect to our test hub, a new instance will be created and will be able to use methods we defined. Let’s do just that; first get signalr-client javascript library using npm:

npm install @aspnet/signalr-client

After invoking this command, ready to use version of the library can be found in node_modules/@aspnet/signalr-client/dist/browser directory. We can put it in some place accessible from the web server (eg. wwwroot/lib directory). Signalr-client package for MVC Core is not and will not be available for bower as the latter one is deprecated – in order to make the process of getting the library automatic, one can use npm scripts or more advanced project like gulp or webpack – but this is out of the scope of this post.

Now we’re ready to demonstrate how our hub can be used in a simple script:

@section Scripts {
    <script src="~/lib/signalr-client/signalr-client.min.js"></script>
    <script>
        let testHubConnection = new signalR.HubConnection('/testHub');
 
        testHubConnection.on('send', data => {
            alert(data);
        })
 
        testHubConnection.start()
                         .then(() => testHubConnection.invoke('send', testHubConnection.connection.connectionId));
    </script>
}

(In this sample project I put it in a new TestHub view and modified Home controller accordingly, such that it can be accessed through /Home/TestHub address. I am also using Razor syntax – the content of “Scripts” section will be put into appropriate place in the layout. For more information you can go to https://docs.microsoft.com/en-us/aspnet/core/mvc/views/layout)

We can verify that SignalR is working correctly by accessing the page with the script in more than one tab – each time the page is requested, new connection id is being shown in all previously opened tabs.

Some more advanced examples

Using dependency injection with SignalR

As I mentioned earlier, new hub instance is created each time a new connection is made. In order to have some kind of persistence, we have to provide appropriate objects to our hub – default dependency injection found with MVC Core can be used here. If our hub class depends on some services:

namespace LearnByDoing.Troika.Services.Lobby
{
    [Authorize]
    public class GameHub : HubWithPresence
    {
        private IGamesInfoService _gameData;
 
        public GameHub(IGamesInfoService gameData, IUserTracker<GameHub> userTracker)
            : base(userTracker)
        {
            _gameData = gameData;
        }

        // (...)

    }
}

We can provide them in Startup class:

namespace LearnByDoing.Troika
{
    public class Startup
    {
 
        // (...)

        public void ConfigureServices(IServiceCollection services)
        {
            // (...)

            services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>));
            services.AddSingleton<IGamesInfoServiceInMemoryGamesInfo>();
        }
    }
}

Sending objects

In the previous example, we used SignalR to send a string (connection ID in our case) from a client to a server, which then propagates this message to all connected clients. Nothing stops us from sending more complicated objects – one must, however, be aware that there’s conversion between names used in C# and JS context (first one being PascalCase, and second camelCase). We can see that already in a basic usage example – where the methods we’re using are named Send in C# code, but we reference them by the name send in JS.

Same thing happens when we refer to objects defined in C#, e.g.:

public class GameInfo
{
    public long GameID { getset; }
    public string GameName { getset; }
    public int MaxPlayers { getset; } = 2;
    public List<string> Players { getset; }
}

If our hub contains a method for getting a collection of such objects:

public IEnumerable<GameInfo> GetGamesInfo()
{
    return _gameData.GetGamesInfo();
}

We can use them in our JS client code, because what we get there is an array containing objects with properties names converted from C#’s ones (GameID gets converted to gameID and so on):

let addRow = gameInfo => {
    $('#lobbyTable').append(
        `<tr id=${gameInfo.gameID}>
        <td>${gameInfo.gameName}</td>
 
        <td>${gameInfo.players[0]}</td>
        <td>${gameInfo.players.join(', ')}</td>
        <td>
            <button class="joinGameButton">Join Game</button>
        </td>
     <tr>`);
}
gameHubConnection.invoke('getGamesInfo').then(gamesList =>
    gamesList.forEach(gameInfo =>
        addRow(gameInfo)
    )
)

Summary

As we saw above, using SignalR Core makes it easy to set up a real-time interactive content within the web application. Further customization is straightforward and SignalR now benefits from .Net Core architecture. Even though we still wait for the official release, it seems that the project is mature enough that it can be used without much headache – especially if the application uses .Net Core and previous version of SignalR cannot be used. Of course, what I showed here is not everything SignalR Core has to offer – perhaps some of them will be shown on this blog later on, as we will be moving on with the project.

Leave a Reply

Your email address will not be published. Required fields are marked *