0

I Implemented the answer on Using Simple Injector with SignalR and my services are successfully resolved, not until OnDisconnected is called on the Hub class. Then I had to follow this question Simple Injector per-web-api-request dependency in SignalR hub as a workaround but get an exception whenever the hub instance is requested.

I get the exception saying:

[SimpleInjector.ActivationException] The registered delegate for type ChatHub threw an exception. The ChatHub is registered as 'Hybrid Web Request / Execution Context Scope' lifestyle, but the instance is requested outside the context of a Hybrid Web Request / Execution Context Scope.

StackTrace:

at SimpleInjector.InstanceProducer.GetInstance()
at SimpleInjector.Container.GetInstance(Type serviceType)
at QuickChat.Hubs.SimpleInjectorHubActivator.Create(HubDescriptor descriptor) in c:\Users\Peter\Documents\Visual Studio 2013\Projects\QuickChat\QuickChat\Hubs\SimpleInjectorHubActivator.cs:line 21
at Microsoft.AspNet.SignalR.Hubs.DefaultHubManager.ResolveHub(String hubName)
at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, StateChangeTracker tracker, Boolean throwIfFailedToCreate)

InnerException: 

at SimpleInjector.Scope.GetScopelessInstance[TService,TImplementation](ScopedRegistration`2 registration)
at SimpleInjector.Scope.GetInstance[TService,TImplementation](ScopedRegistration`2 registration, Scope scope)
at SimpleInjector.Advanced.Internal.LazyScopedRegistration`2.GetInstance(Scope scope)
at lambda_method(Closure )
at SimpleInjector.InstanceProducer.GetInstance()

See below my current code configs. Hub activator:

public class SimpleInjectorHubActivator : IHubActivator
{
    private readonly Container _container;

    public SimpleInjectorHubActivator(Container container)
    {
        _container = container;
    }

    public IHub Create(HubDescriptor descriptor)
    {
        return (IHub)_container.GetInstance(descriptor.HubType);
    }
}

SimpleInjector service registration:

public class SimpleInjectorConfig
{
    public static void Register()
    {
        // Create the container as usual.
        var container = new Container();

        var hybrid = Lifestyle.CreateHybrid(
            () => container.GetCurrentExecutionContextScope() != null,
            new SimpleInjector.Integration.Web.WebRequestLifestyle(),
            new ExecutionContextScopeLifestyle());

        // Register types:
        container.RegisterSingle<MembershipRebootConfiguration>(MembershipRebootConfig.Create);
        container.Register<DefaultMembershipRebootDatabase>(() => new CustomMembershipRebootDatabase());
        container.Register<UserAccountService>(() => new UserAccountService(container.GetInstance<MembershipRebootConfiguration>(), container.GetInstance<IUserAccountRepository>()));
        container.Register<AuthenticationService, SamAuthenticationService>();
        container.RegisterPerWebRequest<IUserAccountQuery, DefaultUserAccountRepository>();
        container.RegisterPerWebRequest<IUserAccountRepository, DefaultUserAccountRepository>();

        container.Register(() => new DataAccess.EF.DataContext(), hybrid);
        container.Register<IUnitOfWork, UnitOfWork>(hybrid);
        container.Register<IUserService, UserService>(hybrid);

        //Register SimpleAuthentication callback provider class
        container.RegisterPerWebRequest<IAuthenticationCallbackProvider, SimpleAuthenticationProviderController>();
        //Register SimpleAuthentication MVC controller.
        container.RegisterPerWebRequest<SimpleAuthenticationController>(
            () => new SimpleAuthenticationController(container.GetInstance<IAuthenticationCallbackProvider>(), null));

        // This is an extension method from the integration package.
        container.RegisterMvcControllers(Assembly.GetExecutingAssembly());
        // This is an extension method from the integration package as well.
        container.RegisterMvcIntegratedFilterProvider();
        //Enable injections to SignalR Hubs
        var activator = new SimpleInjectorHubActivator(container);
        container.Register<ChatHub, ChatHub>(hybrid);
        GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);

        container.Verify();
        //Set dependency resolver for MVC
        DependencyResolver.SetResolver(new SimpleInjectorDependencyResolver(container));
    }
}

Global.asax.cs:

protected void Application_Start()
    {
        SimpleInjectorConfig.Register();
        // Register the default hubs route: ~/signalr/hubs
        RouteTable.Routes.MapHubs();
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
    }
Steven
  • 166,672
  • 24
  • 332
  • 435
pmbanugo
  • 303
  • 3
  • 12

2 Answers2

2

Your hybrid lifestyle is wrong. You should turn the predicate around:

var hybrid = Lifestyle.CreateHybrid(
    () => container.GetCurrentExecutionContextScope() != null,
    new ExecutionContextScopeLifestyle(),
    new SimpleInjector.Integration.Web.WebRequestLifestyle());

Tip: instead of reusing that hybrid variable throughout your composition root, you can also set it as the default scoped lifestyle like this:

container.Options.DefaultScopedLifestyle = hybrid;

This way you can change your registrations to the following:

container.Register<ChatHub, ChatHub>(Lifestyle.Scoped);

This makes your registrations easier and cleaner.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • I still an error but that's just swapping the predicate around just like you said, and without the `DefaultScopedLifestyle` code statement. Do I need to install this package `SimpleInjector.Extensions.LifetimeScoping` ? I can't find a definition for `Lifestyle.Scoped` and DefaultScopedLifestyle. – pmbanugo Nov 04 '15 at 11:59
  • The `Lifestyle.Scoped` and `Options.DefaultScopedLifestyle` are new in Simple Injector v3. You are probably still working with v2, aren't you? You don't need the `LifetimeScoping` package *at all* in your case. Note that the use of `Lifestyle.Scoped` and `Options.DefaultScopedLifestyle` is completely optional; it's just cleaner IMO. – Steven Nov 04 '15 at 12:12
  • Is the new exception completely the same? – Steven Nov 04 '15 at 12:13
  • the error occurs only when a user is disconnecting from the Hub – pmbanugo Nov 04 '15 at 15:02
  • @pmbanugo: Ahh... that's an old problem. Take a look at [this answer](https://stackoverflow.com/a/18487801/264697). It exactly explains the problem and the solution. – Steven Nov 04 '15 at 15:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94218/discussion-between-pmbanugo-and-steven). – pmbanugo Nov 04 '15 at 15:45
1

Like Steven said in his answer, my hybrid lifestyle is wrong. I had to turn the predicate around:

var hybrid = Lifestyle.CreateHybrid(
() => container.GetCurrentExecutionContextScope() != null,
new ExecutionContextScopeLifestyle(),
new SimpleInjector.Integration.Web.WebRequestLifestyle());

After a few discussions with Steven GitHub I arrived at a conclusion.

I had to make my ChatHub a Humble Object and extract all logic from it and put into a separate class that implements a service interface consisting of methods that expose all the logic contained initially within the ChatHub

public interface IChatService
{
    void OnConnected(Guid userId, string connectionId, HubConnectionContext clients);
    void OnDisconnected(Guid userId, string connectionId);
    void Broadcast(string message, string username, HubConnectionContext clients);
}

public class ChatService : IChatService
{
    private readonly IUnitOfWork _unitOfWork;
    public UserChatService(IUnitOfWork unitOfWork) {
        _unitOfWork = unitOfWork;
    }

    public void OnConnected(Guid userId, string connectionId, HubConnectionContext clients) { 
        //Perform other operation using _unitOfWork
    }
    public void OnDisconnected(Guid userId, string connectionId) {
        //Perform other operation using _unitOfWork
    }
    public void Broadcast(string message, string username, HubConnectionContext clients) { 
        //Perform other operation using _unitOfWork and HubConnectionContext
        //broadcast message to other connected clients
        clients.others.broadcast(message);
    }
}

The HubConnectionContext really seems like runtime data to me, so I decided to pass it as a params to the methods.

With this, my hub ChatHub looks lightweight, delegating the calls to the ChatService object, and I no longer experience errors when the hub's OnDisconnected() is called .

And don't forget to register your service with the container:

container.Register<IChatService, ChatService>(hybrid);
pmbanugo
  • 303
  • 3
  • 12
  • This (and the Github link) form a superb answer - I was having the same issue and taking the time to structure my Hub implementation properly sorted out the OnDisconnect exception that was being thrown. Thanks for the complete sample. – keithl8041 Feb 20 '16 at 22:49
  • pmbanugo, do you have a full example of it? i'm stuck at this Thank you – rsegovia Aug 10 '16 at 17:18
  • @rsegovia I've created this [github gist](https://gist.github.com/pmbanugo/99933fc8ec58e96e8e6a7f972ed9e5be). it should help. Don't forget to vote up the question and answer if it was helpful to you. goodluck.. – pmbanugo Aug 11 '16 at 10:44