Get Your Web API Playing Nicely With SignalR on OWIN with Autofac


Niche, right? Yes. But this has caused me a day of headaches so posting here to save anyone else from the pain.

WebAPI

Say we already have Autofac set-up with your WebAPI. So we have something like this:

var builder = new ContainerBuilder();
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly());
// Register the Autofac middleware FIRST, then the Autofac Web API middleware,
// and finally the standard Web API middleware.
app.UseAutofacMiddleware(container);
app.UseAutofacWebApi(config);
app.UseWebApi(config);

This is fine.

If we then follow the Autofac docs, we’ll end up with something pretty similar for SignalR:

var builder = new ContainerBuilder(); // same as the one used for WebAPI
builder.RegisterHubs(Assembly.GetExecutingAssembly());

and then the Hub configuration:

// Get your HubConfiguration. In OWIN, you'll create one
// rather than using GlobalHost.
var config = new HubConfiguration();
 
// Register your SignalR hubs.
builder.RegisterHubs(Assembly.GetExecutingAssembly());
 
// Set the dependency resolver to be Autofac.
var container = builder.Build();
config.Resolver = new AutofacDependencyResolver(container);
 
// OWIN SIGNALR SETUP:
 
// Register the Autofac middleware FIRST, then the standard SignalR middleware.
app.UseAutofacMiddleware(container);
app.MapSignalR("/signalr", config);

Run this and (if you just have a default WebAPI set-up) we’ll probably get a 404 for our SignalR hub. So we need to get WebAPI to ignore anything relevant to SignalR.

Ignore Routes

Before we map our main routes, we can tell the router to ignore anything under the SignalR path:

// Ignore anything to do with SignalR
config.Routes.IgnoreRoute("signalr", "signalr/{*pathInfo}");
 
// Rest of the routes
config.Routes.MapHttpRoute(
    "Default",
    "{controller}/{action}/{*id}",
    new {id = RouteParameter.Optional});

Here, we’re just telling the WebAPI router to ignore anything that starts with ‘signalr‘. Now our hub paths should return properly.

But what about the dependency config?

Path Matching Config

You might have noticed that we have two calls to app.UseAutofacMiddleware(container) – which one do we use?

Rather than configuring everything directly onto the app object, we can use path matching to only inject the required Autofac dependency settings for SignalR.

Using this syntax, we can set up our code with a scoped middleware call:

app.Map("/signalr", map =>
{
    map.UseAutofacMiddleware(container);
 
    var hubConfiguration = new HubConfiguration
    {
        Resolver = new AutofacDependencyResolver(container),
    };
 
    map.RunSignalR(hubConfiguration);
});

(this replaces app.MapSignalR from above)

Note that we’re setting the SignalR path explicitly as /signalr. We could change this if we wanted, but we’d need to also update our ignored routes above to match.

With that done, we can inject whatever we need from our container. The Autofac docs suggest injecting an ILifetimeScope into the Hub constructor so child objects can be disposed of properly (we do not want a memory leak) so it’s worth reading that if you haven’t already.

public class MyHub : Hub
{
    private ILifetimeScope _hubLifetimeScope;
 
    public MyHub(ILifetimeScope scope)
    {
        _hubLifetimeScope = scope;
    }
 
    public void Ping(string text)
    {
        var clock = _hubLifetimeScope.Resolve<IClock>();
        Clients.Caller.Pong(clock.GetTime());
    }
 
    protected override void Dispose(bool disposing)
    {
        // Dispose the hub lifetime scope when the hub is disposed.
        if (disposing && _hubLifetimeScope != null)
        {
            _hubLifetimeScope.Dispose();
        }
 
      base.Dispose(disposing);
    }
}