Skip to content

Instantly share code, notes, and snippets.

@lethek
Last active March 27, 2017 22:22
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lethek/5933672 to your computer and use it in GitHub Desktop.
Save lethek/5933672 to your computer and use it in GitHub Desktop.
Using Autofac to inject dependencies into SignalR hubs and manage dependency lifetimes: management of the lifetimes of dependencies will happen automatically as long as the Hub inherits from AirtimeHub and the dependencies are registered with Autofac with a InstancePerLifetimeScope() option.
using System;
using Microsoft.AspNet.SignalR;
namespace Airtime.Hubs
{
/// <summary>
/// Hubs that have injected dependencies which must be scoped to the same lifetime should
/// inherit this class to enable automatic lifetime scope management and prevent memory leaks.
/// </summary>
public abstract class AirtimeHub : Hub
{
internal event EventHandler Disposing;
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing) {
var handler = Disposing;
if (handler != null) {
handler(this, EventArgs.Empty);
}
}
}
}
}
using Autofac;
using Microsoft.AspNet.SignalR.Hubs;
namespace Airtime.Hubs.Framework
{
/// <summary>
/// SignalR HubActivator for creating hub instances with the correct lifetime-scope for automatic per-instance session & transaction management.
/// </summary>
internal class AirtimeHubActivator : IHubActivator
{
public AirtimeHubActivator(AirtimeHubLifetimeScopeManager hubLifetimeScopeManager, ILifetimeScope lifetimeScope)
{
LifetimeScope = lifetimeScope;
HubLifetimeScopeManager = hubLifetimeScopeManager;
}
public IHub Create(HubDescriptor descriptor)
{
return typeof(AirtimeHub).IsAssignableFrom(descriptor.HubType)
? HubLifetimeScopeManager.CreateHub<AirtimeHub>(descriptor.HubType, LifetimeScope)
: LifetimeScope.Resolve(descriptor.HubType) as IHub;
}
private ILifetimeScope LifetimeScope { get; set; }
private AirtimeHubLifetimeScopeManager HubLifetimeScopeManager { get; set; }
}
}
using System;
using System.Collections.Generic;
using Autofac;
using Microsoft.AspNet.SignalR.Hubs;
namespace Airtime.Hubs.Framework
{
internal class AirtimeHubLifetimeScopeManager
{
public T CreateHub<T>(Type type, ILifetimeScope lifetimeScope) where T : AirtimeHub
{
var scope = lifetimeScope.BeginLifetimeScope();
var hub = (T)scope.Resolve(type);
hub.Disposing += HubOnDisposing;
_hubLifetimeScopes.Add(hub, scope);
return hub;
}
private void HubOnDisposing(object sender, EventArgs eventArgs)
{
ILifetimeScope scope;
var hub = sender as IHub;
if (hub != null && _hubLifetimeScopes.TryGetValue(hub, out scope)) {
_hubLifetimeScopes.Remove(hub);
if (scope != null) {
scope.Dispose();
}
}
}
private readonly Dictionary<IHub, ILifetimeScope> _hubLifetimeScopes = new Dictionary<IHub, ILifetimeScope>();
}
}
using System;
using System.Linq;
using Airtime.Core;
using Airtime.Core.Framework.Security;
using Airtime.Core.Framework.SignalR;
using Airtime.Data;
using Airtime.Data.Client;
using Airtime.Data.Account;
using NLog;
namespace Airtime.Hubs
{
//membershipRepository is injected by Autofac and as long as this hub subclasses AirtimeHub,
//membershipRepository can be bound to the same lifetime as the hub and destroyed at the same time
//(if the dependency has been registeredd with Autofac with the InstancePerLifetimeScope() option)
public class ExampleHub : AirtimeHub
{
public ExampleHub(IMembershipRepository membershipRepository)
{
MembershipRepository = membershipRepository;
}
public virtual void Echo(string message)
{
var principal = Context.User as AirtimePrincipal;
if (principal != null && principal.Identity.MembershipId.HasValue) {
var user = MembershipRepository[principal.Identity.MembershipId.Value];
Clients.All.AddMessage("From " + user.Username + ": " + message);
} else {
Clients.All.AddMessage(message);
}
}
public IMembershipRepository MembershipRepository { get; set; }
}
}
using System;
using Airtime.Hubs.Framework;
using Autofac;
using Microsoft.AspNet.SignalR.Hubs;
namespace Airtime.Hubs
{
public class HubsModule : Module
{
protected override void Load(ContainerBuilder builder)
{
if (builder == null) {
throw new ArgumentNullException("builder");
}
builder.RegisterType<AirtimeHubLifetimeScopeManager>().SingleInstance();
builder.RegisterType<AirtimeHubActivator>().As<IHubActivator>().SingleInstance();
//Register SignalR hubs (must be marked ExternallyOwned so that instances will be destroyed by SignalR rather than Autofac)
builder.RegisterAssemblyTypes(typeof(Airtime.Hubs.AssemblyHook).Assembly)
.Where(t => !t.IsAbstract && t.IsClass && t.IsAssignableTo<IHub>())
.InstancePerDependency()
.ExternallyOwned();
//Register an example dependency that will be injected into the ExampleHub
builder.RegisterType<Airtime.Data.Hibernate.MembershipRepository>
.As<IMembershipRepository>()
.InstancePerLifetimeScope();
}
}
}
@jasonhjohnson
Copy link

Hi,

Thanks for putting this together. Where does the HubsModule file get called from? I have all of my Autofac registrations in one ContainerConfig class as follows...

/// <summary>
/// ContainerConfig Class.
/// </summary>
public partial class ContainerConfig
{       
    /// <summary>
    /// Register container
    /// </summary>
    public static void RegisterContainer(HttpConfiguration config)
    {
        var builder = new ContainerBuilder();

        // Load and set references to assemblies we'll need below
        var dataAssembly = Assembly.Load("MyProject.Data");
        var serviceAssembly = Assembly.Load("MyProject.Service");

        // MVC Registrations
        builder.RegisterControllers(Assembly.GetExecutingAssembly());      

       ....additional registrations

@jasonhjohnson
Copy link

Actually, I got that part figured out. However, I'm not able to resolve the AssemblyHook portion of the registration. Where does that come from?

@patroza
Copy link

patroza commented Apr 17, 2014

Thanks a lot for this. I've created another solution for MEF today which was much less elegant, and actually didn't make the hubs composition root, but instead the Mediator that was imported into every hub.

Here's a MEF variant of your much better implementation, incase someone finds it useful;
https://gist.github.com/sickboy/10999735

@sstorie
Copy link

sstorie commented Jun 26, 2015

This is very helpful in clearing up the confusing (to me anyway) DI techniques required for SignalR. I'm curious, did you ever take this and extend it to work with the generic Hub class?

I'm trying to refactor this to support using an explicit interface that defines the client calls, but am stuck at the autofac registration step of things.

@lethek
Copy link
Author

lethek commented Sep 4, 2015

Apologies for not noticing anybody's comments on this Gist until now, I'd never expected anybody to stumble across it.

I ended up developing the idea a little further in this repository: https://github.com/lethek/SignalR.Extras.Autofac

And released it on Nuget here: https://www.nuget.org/packages/SignalR.Extras.Autofac

@jasonhjohnson AssemblyHook was just a class I used to conveniently reference an assembly. I could've instead used any type defined in the assembly that I wanted to reference though.

@sstorie No, I'd made a note late last year to do that, but it somehow fell off my radar. I'm glad for the reminder, I'll start looking into it soon... Did you ever get anywhere with it?

@lethek
Copy link
Author

lethek commented Sep 5, 2015

@sstorie I've now implemented support for the generic SignalR Hub class. It's in the latest v1.1.0 (https://github.com/lethek/SignalR.Extras.Autofac/). Just sub-class LifetimeHub instead of LifetimeHub.

@lethek
Copy link
Author

lethek commented Sep 5, 2015

@Sickboy Thanks for that. I've actually just started using MEF for the first time in a project earlier this week. I've got a few things to learn there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment