Software Development

Cleaner code for SignalR – Strongly typed hub client proxies

Notify your users in real-time with SignalR

Have you ever tried the powerful SignalR framework from Microsoft? Then have you probably written some not too clean code for it, too.

With web sockets, SignalR provides real-time updates to web application users. If web sockets are not supported by the browser, the framework conveniently falls back to one of many supported alternative technologies which allow a push (or push-like) communication pattern. Creating real-time web applications requires little effort from you thanks to abstraction of the communication layer and user management.

When you start with something new and you escape the world of carefully chosen examples and tutorials, you run across issues when dealing with day-to-day requirements, which are not covered in any example. Often those will take you days to figure out.

What I ran into was an issue when using the hub in a different controller: Resolving the hub instance is easy, but you will produce dirty code. The many examples out there do not cover this code quality concern. Before I show you how to create a clean code solution, let us have a deeper look at the problem.

Example application

Scenario

We are about to create a real-time app for a fire station. The fire department has an existing application which manages fire incidents. Our task is to connect the existing management app with our new real-time app, so that users see the incidents immediately. The existing system will call a newly created web service to push incidents.

Real-time app

The real-time app is created using ASP.NET Web API 2 combined with SignalR 2. We use Ninject for dependency injection.

You can find a live demo on Azure here.

Fire Incident System

Fire Incident System

First we create a hub that is responsible for notifying the user about incidents in real-time. The hub consists of one method which pushes incidents to the connected SignalR clients.

The existing incident management communicates with our app by calling a web service whenever an incident occurs. In the controller of that web service, we then use the hub to inform the clients. That makes it necessary to resolve the hub instance in the controller of the web service. As I mentioned earlier, resolving the instance is pretty simple:

    public class FireIncidentHub : Hub
    {
        public void NotifyAllUsers(FireIncident incident)
        {
            Clients.All.notify(incident);
        }
    }
    public class FireIncidentsController : ApiController
    {
        public IHttpActionResult Post(FireIncident fireIncident)
        {
            var hub = GlobalHost.ConnectionManager.GetHubContext<FireIncidentHub>();
            return Ok();
        }
    }

Any incident will end up in our FireIncidentsController which then resolves the hub instance. Looks good? It does, except that we yet have to inform the clients.

Calling NotifyAllUsers will do that for us but that does not work, because hub is not an instance of FireIncidentHub.It is an object implementing IHubContext. Fortunately, IHubContext offers access to the clients:

   var hub = GlobalHost.ConnectionManager.GetHubContext<FireIncidentHub>();
   hub.Clients.All.notify(fireIncident);

Looks good? It does, except that it also looks very familiar, right? Yes, we wrote the same code already in the hub. And this leads us to the dirt in our code.

The dirt

  1. The object representing the hub instance is dynamic. We have to assume that the client has implemented a method notify(fireIncident). It is bad enough to have to make this assumption in hub. Expanding these assumptions to other classes makes our solution more error-prone.
  2. Violation of DRY: We say the same thing twice

Cleaning up

Solution for problem 1

To get rid of the dynamic and therefor weakly typed object, SignalR allows us to specify an interface for the client proxy. We can enforce a strongly typed version of the client.

            var hub = GlobalHost.ConnectionManager.GetHubContext<FireIncidentHub, IFireIncidentHub>();
            hub.Clients.All.notify(fireIncident);

Even better, we can achieve the same in the hub itself by using the generic version of the Hub class. Calls to the clients will now be based on a strongly typed object.

    public class FireIncidentHub : Hub<IFireIncidentHub>;

There we go, problem 1 solved.

Solution for problem 2

We are still saying the same thing twice. Both controller and hub are responsible for notifying all clients. At this point, you should consider whether your application logic provides the solution. If you can answer one of the two below questions with yes, the problem is solved (note that both cannot be true at the same time):

  1. Can you remove the implementation of notifying clients from the hub class? Methods in the hub are accessible only to clients using SignalR.
  2. Could the existing management application use SignalR to inform the clients (as described in 1.), not taking the detour and calling the web service which then uses the hub?

If none of the above solutions is viable, then our architect brain will guide us towards cleaner code: We need to decouple the communication between controller and hub. Depending on the messaging and communication infrastructure used in your project, there could a 100’s of ways of doing that. In our example, we will use a minimalist service bus, which relies on the event mechanism from the .NET framework. An event will be fired from the controller for any new incident, and handled by the hub so that users will be notified. Below you find the class responsible for the notification.

    public class FireIncidentNotify
    {
        public event EventHandler<FireIncident> FireIncident;

        public void Raise(FireIncidentRealtime.Models.FireIncident fireIncident)
        {
            if (FireIncident != null)
                FireIncident(this, fireIncident);
        }
    }

That is it. The controller will fire a fire event :), the hub will handle it. No duplicate code, instead we have single responsibility. Problem 2 solved.

You can find the complete solution on Github.

Summary

SignalR allows to add real-time communication to your project lightning-fast. That applies both to the development time and the time it takes to notify users. By adding an interface to the client proxy and avoiding duplicate code, your solution will be less error-prone and cleaner.

Resources

http://www.asp.net/signalr/overview/getting-started/tutorial-getting-started-with-signalr

http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api

http://www.ninject.org/

http://fireincident.azurewebsites.net/Fires

https://github.com/cleanercode/signalRHubClean

10 replies »

    • I think I know where you are coming from, Pavel. Both ASP.NET and SignalR use a property called DependencyResolver, which, once set, will be used for the Dependency Injection. However, both are in different namespaces, which makes it a bit tricky in the beginning.

      Like

    • If you prefer weakly typed languages, that is fine, If it works for you, that’s even better. JavaScript is a very popular weakly typed language. On the other hand, TypeScript shows the demand for strongly typed solutions.
      I have been working for many years both with C# and with Smalltalk. C# was strictly strongly typed until LINQ and JSON support required dynamic paradigms. Smalltalk is about the weakest typed language you can imagine. Comparing my experiences, I can say that especially with increasing size of teams, weakly typed languages require a lot of discipline, conventions and rules. It makes debugging harder and often requires more skilled developers. It looks easier on the surface, but the larger the codebase, the more discpline will be necessary.
      Bottom line: In C#, I can reduce the probability of runtime-errors with the help of an interface (and thus not “blindly” invoking method).

      Liked by 1 person

  1. But what happens if clients 1 and 2 connect and then client 1 disconnects, won’t there no longer be a hub that is receiving the events from the notifier as client2 won’t have registered the event handler as client1 already did?

    Liked by 1 person

    • Excellent question, Bob.

      Your question points to the same underlying peculiarity which is touched in problem 1:
      The instance of the FireIncidentHub class is not the same object which is generated for each connected client. The instance of FireIncidentHub (or any Hub class you have implemented) is a singleton, it exists once for your web application. For each connected client, an instance of a class implementing IHubContext is generated. So you have one instance of FireIncidentHub, and for each client an instance of a class implementing IHubContext.

      For your question of multiple clients connecting and disconnecting, this means the following:
      The first time a client connects, the singleton instance of FireIncidentHub is generated, the constructor registering the event handling for the Notifier is invoked. For further clients connecting, the constructor is not called because the singleton already exists. Since the FireIncidentHub instance is a singleton instance responsible for each connected client, connecting or disconnecting causes no issues, as you might have thought.

      Like

  2. Thanks David for the excellent work. This is the only solution that works when I want to call a hub method from a controller, or rather send a notification directly from the controller. However, I have a problem with the latest version of libraries. signalR [and dependent] > 2.2.0 and jQuery > 2.2.4 give a runtime error. Do you know what could be the cause? I am using the VS 2017 community edition. message content during debug is:
    Unhandled exception at line 1, 83082 in http: // localhost: 58112 / bundles / jquery? V = MRjVrMuK9DXe6nW0tFmw9cj1pT5oo4Jf-eJQmGfwEF01
    0x800a01b6 – JavaScript Runtime Error Time: Object does not support property or method “indexOf”
    and
    Unhandled exception at line 18, column 9 in http: // localhost: 58112 / signalr / hubs
    0x800a139e – JavaScript Runtime Error: SignalR: SignalR is not loaded. Please ensure jquery.signalR-x.js is referenced before ~ ​​/ signalr / js.
    When I change libraries on SignalR 2.2.0 and jQuery 2.2.4 everything works fine

    Like

    • Hello Kowalski,

      thank you for your feedback.

      I have not yet tried running the solution with the later versions of SignalR and jQuery.

      You might be curious to try out ASP.NET Core 2.0 with the recently released SignalR Core version. It works, and it does not require jQuery. I will publish a core version of this solution soon.
      Let me know your results. Also let me know if .NET Core is not an option for you, in that case I will see if I can reproduce the error that you’ve mentioned.

      Like

      • It’s been over 24 hours since I’ve been trying to find a solution to the problem. Changes to the version of the attached libraries have led to the appearance of further errors. This is a strange behavior, but after all the attempts I decided to go back to the beginning. I unzipped your archive with source files, I uploaded SignalR 2.2.0 and JQuery 2.2.4. the other libraries in the latest versions. Previously in this configuration everything worked, but now a new error has occurred. very strange. I get an internal server error [500] “An error occurred when trying to create a controller of type ‘FireIncidentsController’. Make sure that the controller has a parameterless public constructor.” When I change Ninject [and dependent libraries] to lower versions, the program starts and displays the initial data from the repository, but adding a new incident remains without action. When debugging, I noticed that ‘Notifier’ in the ‘Post’ method is always null [in ‘FireIncidentsController’]. I give up. Too many conflicts in linked libraries. I do not know Asp.Net Core as I would like, so I have to stay with the current technology because I do not have much time for my project. I will learn Core in the meantime. Maybe then I will come back to your solution. Once again thank you for sharing your work.

        Liked by 1 person

  3. well okay. as usual it was a coincidence. I have not worked with jQuery for over a year [probably v.1.10 lately]. I went to complete novice because I did not look first at the jQuery portal. probably because I thought the error lies more on the SignalR side. Now everything works with the latest versions of the packages and I am posting step by step directions. The jQuery >= 3 version has breaking changes, and the jQuery team has released the ‘jQuery Migrate’ plugin, [more here: https://github.com/jquery/jquery-migrate/#readme%5D
    so, after you extract the source files from the David’s project, you need to install all The latest versions of the packages, through the NuGet Manager, in addition to jQuery, which you must install in version 2.2.4. rebuild and run project. Then install jQuery.Migrate version 3.0.0 [uncompressed development version]. It’s all about packages. Warning! Be careful when installing ‘Ninject.Web.Common.WebHost’. During installation you will be asked if you want to overwrite the file ‘NinjectWebCommon.cs’. answer ‘NO’. I, by haste, answered ‘yes’ and therefore received ‘strange errors’ about which I wrote earlier. I just had the default Ninject file instead of that one from original project.
    Then, in the ‘BundleConfig.cs’ file, complete the existing entry. You should get this:
    bundles.Add (new ScriptBundle (“~ / bundles / jquery”). Include (
    “~ / Scripts / jquery- {version}.js”,
    “~ / Scripts / jquery-migrate-3.0.0.js”));
    F5 and everything should work. Now you can remove the above entry “~ / Scripts / jquery-migrate-3.0.0.js”, uninstall JQuery.Migrate, and update jQuery to the latest version via NuGet Manager. that’s all. so simple and so many problems because of my rush 🙂

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s