SignalR: Logging exceptions using a custom HubPipeline Module

Hackered
Tuesday, March 17, 2015
by Sean McAlinden

When things go wrong with complex rich client applications it can be difficult to monitor and diagnose, especially when using complex client-server communication approaches such as websockets, SignalR offers a nice way to hook into its pipeline and add custom exception logging and trace handling.

Whilst we could litter our code with try-catch blocks to handle quite a few hub and client scenarios, it is much cleaner to deal with unhandled exceptions within the pipeline itself.

Step 1 - Create a pretty flakey hub method

Let's create a hub method which is destined to fail miserably:

public class MyErrorHub : Hub
{
    public void FailingCall()
    {
        throw new Exception("There has been an error");
    }
}

So not the greatest hub method but will certainly do what we need in this case: i.e. break.

Step 2 - Create some client code to call the hub

Lets create some client code which will call the hub and alert on the failure (probably best to handle this more gracefully in your real apps).

<script type="text/javascript">
    $(function() {
        var myErrorHub = $.connection.myErrorHub;
        $.connection.hub
            .start()
            .done(function() {
               myErrorHub.server.failingCall()
                .fail(function(e) {
                  alert(e);
            });
        });
    });
</script>

This should do the trick.

Step 3 - Add a custom HubPipelineModule to log exceptions

At this point we shoud have a fully working (failing) hub call with a generic error being returned (providing you have done the usual signal setup such as scripts and app.MapSignalR() in your startup class).

What we don't have is some useful logging around the exception being thrown.

Lets create a basic module to hook into the exception message being thrown:

public class SignalrErrorHandler : HubPipelineModule
{
    protected override void OnIncomingError(ExceptionContext exceptionContext, IHubIncomingInvokerContext invokerContext)
    {
        // Replace this with your chosen logger
        Trace.WriteLine(exceptionContext.Error.Message);
        base.OnIncomingError(exceptionContext, invokerContext);
    }
}

As you can see, the exceptionContext can be used to interrogate the thrown exception and perform some logging / trace.

Step 4 - Hook up the custom error handling HubPipelineModule

The last thing we need to do is hook up the custom HubPipelineModule we've created, this is achieved in the startup class.

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        GlobalHost.HubPipeline.AddModule(new SignalrErrorHandler());

        app.MapSignalR();
    }
}

That's pretty much it, a nice and clean way to catch unhandled exceptions within the SignalR pipeline.