22

I am using SignalR in my MVC3 application, and since I have implemented StructureMap Dependency Injection on my controllers I would like to do the same in my hub, but I can't seem to get it working.

Please tell me what's wrong with my codes below:

SignalRSmDependencyResolver.cs

public class SignalRSmDependencyResolver : DefaultDependencyResolver
{
    private IContainer _container;

    public SignalRSmDependencyResolver(IContainer container)
    {
        _container = container;
    }

    public override object GetService(Type serviceType)
    {
        object service = null;
        if (!serviceType.IsAbstract && !serviceType.IsInterface && serviceType.IsClass)
        {
            // Concrete type resolution
            service = _container.GetInstance(serviceType);
        }
        else
        {
            // Other type resolution with base fallback
            service = _container.TryGetInstance(serviceType) ?? base.GetService(serviceType);
        }
        return service;
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        var objects = _container.GetAllInstances(serviceType).Cast<object>();
        objects.Concat(base.GetServices(serviceType));
        return objects;
    }
}

SignalRExtensionsRegistry.cs

public class SignalRExtensionsRegistry : Registry
{
    public SignalRExtensionsRegistry()
    {
        For<IDependencyResolver>().Add<SignalRSmDependencyResolver>();
    }
}

IoC.cs

public static class IoC {
    public static IContainer Initialize() {

        var container = BootStrapper.Initialize();

        container.Configure(x =>
        {
            x.For<IControllerActivator>().Singleton().Use<StructureMapControllerActivator>();
        });

        return container;
    }
}

public class StructureMapControllerActivator : IControllerActivator {
    public StructureMapControllerActivator(IContainer container) {
        _container = container;
    }

    private IContainer _container;

    public IController Create(RequestContext requestContext, Type controllerType) {
        IController controller = DependencyResolver.Current.GetService(controllerType) as IController;
        return controller;
    }
}

AppStart_Structuremap.cs

[assembly: WebActivator.PreApplicationStartMethod(typeof(StoreUI.AppStart_Structuremap), "Start")]

namespace MyNameSpace {
public static class AppStart_Structuremap {
    public static void Start() {
        var container = (IContainer) IoC.Initialize();
        DependencyResolver.SetResolver(new StructureMapDependenceyResolver(container));
        AspNetHost.SetResolver(new StructureMapDependencyResolver(container));            
    }
}
}

NotificationsHub.cs

[HubName("notificationsHub")]
public class NotificationsHub : Hub
{    
    #region Declarations
    private readonly IUserService userService;
    #endregion

    #region Constructor

    public NotificationsHub(IUserService userService)
    {
        this.userService = userService;
    }

    #endregion

    public void updateServer(string message)
    {
        Clients.updateClient(message);
    }
}

Thanks

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
dmc
  • 807
  • 2
  • 10
  • 25
  • What dependency injection framework are you using? – Andy May Mar 20 '12 at 18:45
  • @AndyMay I'm using structuremap. – dmc Mar 20 '12 at 23:05
  • Based on what you have added here, all you should need is the updated DependencyResolver code, that way you can resolve concrete types that get passed into your resolver (Hubs are requested as concrete types). – Gary.S Mar 26 '12 at 04:59
  • There is a typo in your StructureMapResolver constructor should be `public StructureMapResolver` or the class should be `SignalRSmDependenceyResolver` – Gary.S Mar 26 '12 at 05:11
  • Please see updated code @Gary.S. I added your registry class SignalRExtensionsRegistry.cs – dmc Mar 26 '12 at 05:20
  • @Gary.S It's now working!!! :) – dmc Mar 26 '12 at 05:33
  • @DuncanMcIntyre 2 things, first you should update your SignalRSmDependencyResolver class to mimic the one I posted (updated recently). This is because TryGetInstance will not resolve concrete types, but GetInstance will. Second, you need to make sure your registrations get into the container: http://structuremap.net/structuremap/RegistryDSL.htm#section2 – Gary.S Mar 26 '12 at 05:33
  • Thanks Sir! Sorry I missed your updated code inside GetService, and it did the fix! Thanks! – dmc Mar 26 '12 at 05:35
  • Glad to help, I also hang out on jabbr from time to time – Gary.S Mar 26 '12 at 05:36
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/9300/discussion-between-duncan-mcintyre-and-gary-s) – dmc Mar 26 '12 at 05:36

4 Answers4

8

Getting Structuremap into SignalR is actually pretty easy. First you want to create your own resolver:

StructureMap Resolver

Usings:

using SignalR.Infrastructure;
using StructureMap;

Class:

public class StructureMapResolver : DefaultDependencyResolver
{
    private IContainer _container;

    public StructureMapResolver(IContainer container)
    {
        _container = container;
    }

    public override object GetService(Type serviceType)
    {
        object service = null;
        if (!serviceType.IsAbstract && !serviceType.IsInterface && serviceType.IsClass)
        {
            // Concrete type resolution
            service = _container.GetInstance(serviceType);
        }
        else
        {
            // Other type resolution with base fallback
            service = _container.TryGetInstance(serviceType) ?? base.GetService(serviceType);
        }
        return service;
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        var objects = _container.GetAllInstances(serviceType).Cast<object>();
        return objects.Concat(base.GetServices(serviceType));
    }
}

The idea here is to try and use your container to resolve the dependencies, if you do not have the dependency wired up, pass it through to the default resolver. This way you don't have to worry about all of the other dependencies in SignalR and can focus only on the stuff you want to inject into (Hubs, ConnectionIdFactory, MessageBus, etc.).

Bindings for Resolver and Hub

Next you will want to register this in your container (i like using registries):

Usings:

using SignalR.Infrastructure;
using StructureMap.Configuration.DSL;

Class:

public class ExtensionsRegistry : Registry
{
    public ExtensionsRegistry()
    {
        For<IDependencyResolver>().Add<StructureMapResolver>();
    }
}

Resolver Replacement

Finally you will want to tell SignalR to use your resolver instead of the default:

Global::Application_Start or WebActivator::Pre_Start

Usings:

using SignalR.Hosting.AspNet;
using SignalR.Infrastructure;

Application_Start:

// Make sure you build up the container first
AspNetHost.SetResolver(StructureMap.ObjectFactory.GetInstance<IDependencyResolver>());

Silly Hub with injected dependencies

Now you can just inject any dependencies your container knows about into the hubs themselves:

[HubName("defaultHub")]
public class DefaultHub : Hub, IDisconnect
{
    private readonly IRepository _repo;
    public DefaultHub(IRepository repo)
    {
        _repo = repo;
    }

    public void Connect()
    {
        Caller.setUser(Context.ConnectionId);
        Clients.addMessage(string.Format("{0} has connected", Context.ConnectionId));
    }

    public void MessageSender(string message)
    {
        Caller.addMessage(_repo.RepositoryMessage());
        Clients.addMessage(message);
    }

    public Task Disconnect()
    {
        var clientId = this.Context.ConnectionId;
        return Task.Factory.StartNew(() => { Clients.addMessage(string.Format("{0} has disconnected", clientId)); });
    }
}
Gary.S
  • 7,076
  • 1
  • 26
  • 36
  • Thanks @Gary.S . I actually have tried implementing it with signalR this way but I used var container = (IContainer) IoC.Initialize(); AspNetHost.SetResolver(new StructureMapDependencyResolver(container)); in AppStart. Now I tried your AspNetHost.SetResolver(StructureMap.ObjectFactory.GetInstance()); on AppStart but I'm getting Invalid Arguments – dmc Mar 26 '12 at 04:07
  • Let me update my post Gary and show you my code for DI. Thanks – dmc Mar 26 '12 at 04:12
  • @DuncanMcIntyre Ok, so hub registration can be avoided but your resolver class is going to get a little uglier, sample updated. – Gary.S Mar 26 '12 at 04:50
  • The DI is now working Sir. This I can tell, because I no longer get a js error on my signalR client methods (not like before as SignalR becomes undefined in my js). Now the next issue is I can no longer call my Hub methods from the client side. I tried this on a button click. – dmc Mar 26 '12 at 05:04
  • This is getting weird because I used to test the same button to call my Hub methods before I implemented the DI on SignalR – dmc Mar 26 '12 at 05:05
  • @DuncanMcIntyre That is typically an indication that the container is not resolving the hub correctly. Can you post your updated code? – Gary.S Mar 26 '12 at 05:07
  • Ok @Gary.S , give me a few minutes. Thanks – dmc Mar 26 '12 at 05:10
  • I'm not sure if I was doing something wrong, but I could not get this working (SignalR 0.5). I suspect that it was falling back to default behavior at some point before reaching IHubActivator, meaning a default HubActivator was used rather than my registered one. To get dependencies injected into hubs I needed to register an IHubActivator backed by StructureMap with the default dependency resolver. The SM DependencyResolver would probably have worked if I had registered types all the way down to where IHubActivator is needed, but this didn't really buy me anything so I took the shortcut. – AlexCuse May 14 '12 at 18:03
  • Your GetServices does a call to Concat, but does nothing with the result. Maybe you should return the enumerable coming out of Concat? Right now you are just returning GetAllInstances casted to object. – David Cumps Dec 11 '12 at 09:38
  • @DavidCumps right you are, fixed even though its an old answer and doesn't really apply to the newer versions of SignalR – Gary.S Dec 11 '12 at 09:58
5

Have you followed the instructions here:- https://github.com/SignalR/SignalR/wiki/Extensibility ?

You'll need to use AspNetHost.SetResolver.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133
  • I now have AspNetHost.SetResolver(new StructureMapDependencyResolver(container)); on AppStart, but still can't connect to my Hub with the DI implementation code above with the IUserService. I've been googling for sample implementations on SignalR.Structuremap but I can't find any. Any samples you can point me to? Thanks. – dmc Mar 23 '12 at 15:35
2

I know this is an old thread, but for those who are wondering where is the AspNetHost.SetResolver in the newer version of signalR, you can use this in the App_Start StructuremapMvc.cs:

    public static void Start() {
        IContainer container = IoC.Initialize();
        GlobalHost.DependencyResolver = new SignalRSmDependencyResolver(container); // for signalR

        DependencyResolver.SetResolver(new StructureMapDependencyResolver(container));
        GlobalConfiguration.Configuration.DependencyResolver = new StructureMapDependencyResolver(container);
    }
CloneXpert
  • 246
  • 2
  • 9
1

Add something like this to a file in your App_Start folder. This code snippet is for Ninject, so just replace AspNetHost.SetResolver()

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Ninject;
using SignalR.Hosting.AspNet;
using SignalR.Infrastructure;
using SignalR.Ninject;
using Web.Models;

[assembly: WebActivator.PreApplicationStartMethod(typeof(Web.App_Start.NinjectSignalR), "Start")]

namespace Web.App_Start
{
    public static class NinjectSignalR
    {
        public static void Start()
        {
            IKernel kernel = CreateKernel();
            // switch this line to the structuremap resolver
            AspNetHost.SetResolver(new NinjectDependencyResolver(kernel));
        }

        private static IKernel CreateKernel()
        {
            var kernel = new StandardKernel();
            RegisterServices(kernel);
            return kernel;
        }

        private static void RegisterServices(IKernel kernel)
        {
            // add your services here
            //kernel.Bind<IRepository>().To<Repository>();
        }
    }
}
Turp
  • 937
  • 1
  • 6
  • 11
  • I've done this, but when I call var SignalR = $.connection.notificationsHub; SignalR becomes undefined, meaning the connection failed. I don't understand what's wrong here, and I think my Hub code is correct, or is it not? – dmc Mar 26 '12 at 02:39
  • But I don't want this to delay my development, so my implementation now is to call all my SignalR client scripts inside my Controller using IConnectionManager connectionManager = AspNetHost.DependencyResolver.Resolve(); and GetClients. – dmc Mar 26 '12 at 02:43