Share via



April 2018

Volume 33 Number 4

[Cutting Edge]

Discovering ASP.NET Core SignalR

By Dino Esposito | April 2018

Dino EspositoASP.NET SignalR was introduced a few years ago as a tool for ASP.NET developers to add real-time functionality to applications. Any scenarios in which an ASP.NET-based application had to receive frequent and asynchronous updates from the server—from monitoring systems to gaming—were good use cases for the library. Over the years, I used it also to refresh the UI in CQRS architecture scenarios and to implement a Facebook-like notification system in socialware applications. From a more technical perspective, SignalR is an abstraction layer built over some of the transport mechanisms that can establish a real-time connection between a fully compatible client and server. The client is often a Web browser and the server is often a Web server, but both are not limited to that.

ASP.NET SignalR is part of ASP.NET Core 2.1. The overall programming model of the library is similar to that for classic ASP.NET, but the library itself has in fact been completely rewritten. Still, developers should quickly become comfortable with the new scheme, once they adjust to the changes that exist here and there. In this article, I’ll discuss how to use the new library in a canonical Web application to monitor a remote, and possibly lengthy, task.

Setup of the Environment

You may need a couple of NuGet packages to use the library: Microsoft.AspNetCore.SignalR and Microsoft.AspNetCore.SignalR.Client. The former provides the core functionality; the latter package is the .NET client and is needed only if you’re building a .NET Client application. In this case, we’re using it through a Web client, so a SignalR NPM package would be needed instead. I’ll discuss the details of this later in the article. Note that using SignalR in the context of a Web application based on the MVC application model is not a requirement. You can use the services of the SignalR library directly from an ASP.NET Core console application and can also host the server part of SignalR in a console application.

Not surprisingly, the startup class of the application needs to contain some specific code. In particular, you’ll add the SignalR service to the collection of system services and configure it for actual use. Figure 1 presents the typical status of a startup class that uses SignalR.

Figure 1 The Startup Class of a SignalR ASP.NET Core Application

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddMvc();
    services.AddSignalR();
  }
  public void Configure(IApplicationBuilder app)
  {
    app.UseStaticFiles();
    app.UseDeveloperExceptionPage();
    // SignalR
    app.UseSignalR(routes =>
    {
      routes.MapHub<UpdaterHub>("/updaterDemo");
      routes.MapHub<ProgressHub>("/progressDemo");
    });
    app.UseMvcWithDefaultRoute();
  }
}

The configuration of the SignalR service consists in the definition of one or more server routes that bind to one or more endpoints within the server-side environment. The MapHub<T> method links the specified names, which will be part of a requesting URL, to an instance of a hub class. The hub class is the beating heart of the implementation of the SignalR protocol and is where the client calls are handled. You create a hub for each logically related group of calls the server-side intends to accept and handle. A SignalR conversation is made of messages being exchanged between two parties and each party may call methods on the other party receiving no response, one or multiple responses, or just the notification of an error. Any ASP.NET Core SignalR server implementation will expose one or more hubs. In Figure 1, you have two hub classes—UpdaterHub and ProgressHub—bound to unique strings that will be internally used to compose the URL target of the actual calls.

The Hub Class

The hub class in a SignalR application is a plain, simple class that inherits from the base hub class. The base class has the sole purpose of saving developers from writing over and over again the same boilerplate code. The base class only provides some infrastructure but no predefined behavior. In particular, it defines the members in Figure 2.

Figure 2 Members of the Hub Base Class

Member Description
Clients Property that exposes the current list of clients managed by the hub.
Context Property that exposes the current caller context, including information such as the ID of the connection and the claims of the user, if available.
Groups Property that exposes the various subsets of clients that may have been programmatically defined as groups within the full list of clients. A group is typically created as a way to broadcast specific messages to a selected audience.
OnConnectedAsync Virtual method invoked whenever a new client connects to the hub.
OnDisconnectedAsync Virtual method invoked whenever a new client disconnects to the hub.

The simplest hub class is as minimal as the following:

public class ProgressHub : Hub
{
}

Interestingly enough, that’s just the form of the hub if you use it from within a controller method in the context of an ASP.NET Core MVC application. Almost all of the examples you may find about ASP.NET Core SignalR (including the chat example) tend to show a direct and bidirectional binding between the client and the hub without any sort of intermediation by a controller. In this case, the hub will take a slightly more shaped form:

public class SampleChat : Hub
{     
  // Invoked from outside the hub
  public void Say(string message)
  {
    // Invoke method on listening client(s)
    return Clients.All.InvokeAsync("Said", message);
  }
}

Unlike the canonical SignalR chat example that’s rehashed in dozens of blog posts, the example I’ll present here doesn’t really set up a bidirectional conversation between client and server. The connection is established from the client but after that the client won’t send any further requests. The server, instead, will be monitoring the progress of a task and pushing back data to the client whenever appropriate. In other words, the hub class needs to have public methods as the previous code only if the use case requires that the client directly calls into them. If it seems a bit obscure, the following example will shed enough light.

Monitoring a Remote Task

Here’s the scenario: An ASP.NET Core application presents the user some HTML interface for the user to trigger a remote task (say, the creation of a report) that can be lengthy. Because of this, as a developer you want to display a progress bar to provide continuous feedback (see Figure 3).

Using SignalR to Monitor the Progress of a Remote Task
Figure 3 Using SignalR to Monitor the Progress of a Remote Task

As you can guess, in this example both the client and the server are setting up a SignalR conversation live in the context of the same ASP.NET Core project. At this stage of development, you have a fully functional MVC project just extended with the startup code of Figure 1. Let’s set up the client framework. You need to have this setup work done in every Razor (or plain HTML) view that interacts with a SignalR endpoint.

To communicate with a SignalR endpoint from within a Web browser, the first thing you do is add a reference to the SignalR JavaScript client library:

<script src="~/scripts/signalr.min.js">
</script>

You can get this JavaScript file in a number of ways. The most recommended is via the Node.js Package Manager (NPM) tool that’s available on nearly any development machine, espe­cially after Visual Studio 2017. Through NPM, you look for the ASP.NET Core SignalR client named @aspnet/signalr and install it. It copies a bunch of JavaScript files to your disk, only one of which is strictly needed in most scenarios. Anyway, it’s a simple matter of linking a JavaScript file and you can get that file in many other ways, including copying it from an older ASP.NET Core SignalR project. However, NPM is the only supported way the team provides for getting the script. Note also that ASP.NET Core SignalR no longer depends on jQuery.

In the client application, you also need another, more specific, segment of JavaScript code. In particular, you need code like this:

var progressConnection =
  new signalR.HubConnection("/progressDemo");
progressConnection.start();

You create a connection to the SignalR hub that matches the specified path. More precisely, the name you pass as an argument to HubConnection should be one of the names you mapped to a route in the startup class. Internally, the HubConnection object prepares a URL string that results from the concatenation of the current server URL and the given name. That URL is processed only if it matches one of the configured routes. Note also that if client and server are not the same Web application, then HubConnection must be passed the full URL of the ASP.NET Core application that hosts the SignalR hub, plus the hub name.

The JavaScript hub connection object must then be opened up via the start method. You can use JavaScript promises—specifically the then method—or async/await in TypeScript to perform subsequent actions such as initializing some user interface. A SignalR connection is uniquely identified by a string ID.

It’s important to notice that ASP.NET Core SignalR no longer supports automatic reconnect if the transport connection or the server fails. In older versions, in case of server failure the client attempts to re-establish a connection according to a scheduling algorithm and, if successful, it reopens a connection with the same ID. In SignalR Core if the connection drops the client can only start it again via the method start and this results in a different connection instance over a different connection ID.

The Client Callback API

Another fundamental segment of JavaScript code you need is the JavaScript that will be called back by the hub to refresh the interface and reflect on the client that progress is being made on the server. The way you write this code is a bit different in ASP.NET Core SignalR from what it was in older versions, but the intent is exactly the same. In our example, we have three methods that could be called back from the server: initProgressBar, updateProgressBar and clearProgressBar. Needless to say, names and signatures are arbitrary. Here’s a sample implementation:

progressConnection.on("initProgressBar", () => {
  setProgress(0);
  $("#notification").show();
});
progressConnection.on("updateProgressBar", (perc) => {
  setProgress(perc);
});
progressConnection.on("clearProgressBar", () => {
  setProgress(100);
  $("#notification").hide();
});

For example, when the initProgressBar method is called back from the server the helper setProgress JavaScript function will configure the progress bar (the demo uses a Bootstrap progress bar component) and show it. Note that the jQuery library is used in the code but only to update the UI. As mentioned, the client SignalR Core library is no longer a jQuery plug-in. Put another way, if your UI is based on, say, Angular then you may not need to use jQuery at all.

Server-Side Events

The missing piece is the part of the solution that decides when it’s about time to invoke a client function. There are two main scenarios. One is when the client invokes a server action through a Web API or a controller endpoint. The other is when the client directly invokes the hub. In the end, it’s all about where you code the task that calls the client back.

In the canonical chat example, it all happens within the hub: performance of any required logic and dispatch of messages to the appropriate connection. Monitoring a remote task is a different thing. It assumes there’s some business process running in the background that notifies its progress in some way. Technically, you might have this process coded in the hub and set up from there a conversation with the client UI. Or you can have the process triggered by a controller (API) and use the hub only as a way to pass events to the client. Even more realistically than shown in the example, you have the process coded in a layer below the level of controllers.

In a nutshell, you define the hub class and make it available wherever you can decide when and if to invoke client functions. The interesting thing is what’s required to inject the hub instance into a controller or another business class. The demo injects the hub in a controller but it would do exactly the same for another deeper-level class. The sample TaskController is invoked via JavaScript directly from the client to trigger the remote task whose progresses will then move the progress bar:

public class TaskController : Controller
{
  private readonly IHubContext<ProgressHub> _progressHubContext;
  public TaskController(IHubContext<ProgressHub> progressHubContext)
  {
    _progressHubContext = progressHubContext;
  }
  public IActionResult Lengthy()
  {
    // Perform the task and call back
  }
}

You inject a hub in a controller or in any other class via the IHubContext<THub> interface. The IHubContext interface wraps up the hub instance but doesn’t give you direct access to it. From there, you can dispatch messages back to the UI but you can’t access, for example, the connection ID. Let’s say the remote task is performed in the Lengthy method and it’s from there you want to update the client progress bar:

progressHubContext
  .Clients
  .Client(connId)
  .InvokeAsync("updateProgressBar", 45);

The connection ID can be retrieved from within the hub class but not from within a generic hub context as in this example. Hence, the simplest way is for the client method to pass the connection string when it starts the remote task:

public void Lengthy([Bind(Prefix="id")] string connId) { … }

In the end, the controller class receives the SignalR connection ID, is injected the hub context and acts via the context calling methods through a generic non-typed API—the InvokeAsync method. Used in this way, there’s no need to have methods in the hub class! If you find it surprising, well, take a look at the code at bit.ly/2DWd8SV.

Wrapping Up

This article discussed using ASP.NET Core SignalR in the context of a Web application to monitor a remote task. The hub is nearly empty as all the notification logic is built into the controller and orchestrated via the hub context injected via DI. This is just the starting point of a longer tour of ASP.NET Core SignalR. Next up, I’ll delve deeper into the infrastructure and explore typed hubs, as well.


Dino Esposito has authored more than 20 books and 1,000 articles in his 25-year career. Author of “The Sabbatical Break,” a theatrical-style show, Esposito is busy writing software for a greener world as the digital strategist at BaxEnergy. Follow him on Twitter: @despos.

Thanks to the following Microsoft expert for reviewing this article: Andrew Stanton-Nurse


Discuss this article in the MSDN Magazine forum