simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 154 forks source link

SignalR integration #232

Closed sergey-buturlakin closed 6 years ago

sergey-buturlakin commented 8 years ago

Could you add an integration package for SignalR using the following source code?

file SimpleInjectorSignalRDependencyResolver.cs

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.SignalR;

namespace SimpleInjector.Integration.SignalR
{
    public class SimpleInjectorSignalRDependencyResolver : DefaultDependencyResolver
    {
        public SimpleInjectorSignalRDependencyResolver(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public override object GetService(Type serviceType)
        {
            return _serviceProvider.GetService(serviceType) ?? base.GetService(serviceType);
        }

        public override IEnumerable<object> GetServices(Type serviceType)
        {
            var @this = (IEnumerable<object>)_serviceProvider.GetService(
                typeof(IEnumerable<>).MakeGenericType(serviceType));

            var @base = base.GetServices(serviceType);

            return @this == null ? @base : @base == null ? @this : @this.Concat(@base);
        }

        private readonly IServiceProvider _serviceProvider;
    }
}

file SimpleInjectorHubDispatcher.cs

using System;
using System.Threading.Tasks;

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;

namespace SimpleInjector.Integration.SignalR
{
    public class SimpleInjectorHubDispatcher : HubDispatcher
    {
        public SimpleInjectorHubDispatcher(Container container, HubConfiguration configuration) 
            : base(configuration)
        {
            _container = container;
        }

        protected override Task OnConnected(IRequest request, string connectionId)
        {
            return Invoke(() => base.OnConnected(request, connectionId));
        }

        protected override Task OnReceived(IRequest request, string connectionId, string data)
        {
            return Invoke(() => base.OnReceived(request, connectionId, data));
        }

        protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled)
        {
            return Invoke(() => base.OnDisconnected(request, connectionId, stopCalled));
        }

        protected override Task OnReconnected(IRequest request, string connectionId)
        {
            return Invoke(() => base.OnReconnected(request, connectionId));
        }

        private async Task Invoke(Func<Task> method)
        {
            using (_container.BeginExecutionContextScope())
                await method();
        }

        private readonly Container _container;
    }
}

Registration sample:

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var container = new Container();

        container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();

        container.Register<DbContext, MyDbContext>(Lifestyle.Scoped);
        container.Register<ISampleRepository, SampleRepository>(Lifestyle.Scoped);

        // if you want to use the same container in WebApi don't forget to add
        app.Use(async (context, next) => {
            using (container.BeginExecutionContextScope())
                await next();
        });

        // ... configure web api 

        var config = new HubConfiguration
        {
            Resolver = new SimpleInjectorSignalRDependencyResolver(container)
        }

        // ... configure the rest of SignalR

        // important to pass SimpleInjectorHubDispatcher
        app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", config);
    }
}

P.S. I tried to make a PR but experienced problems with the project solution in my dev environment.

dotnetjunkie commented 8 years ago

Thank you for this code. We decided however not to create, publish and maintain an official library for integration with SignalR. Reason for this is that there are simply too many tools, libraries and frameworks that Simple Injector can be integrated with, and adding them all would cause a severe burden on the maintenance of Simple Injector. We have to draw the line somewhere, and we decided to draw the line at the official .NET frameworks (ASP.NET, WCF, etc).

So instead, for integration, Simple Injector users can rely on documentation or unofficial NuGet packages instead. So feel free to create some SignalR.Integration.SimpleInjector package on NuGet. We might also reference this discussion from the documentation (currently, a Stackoverflow answer is referenced).

I've got one question about your implementation though. Currently, your GetServices implementation combines information from both Simple Injector and the base implementation. I wonder whether that is correct. I would say that if services are registered in Simple Injector, SignalR is ignored.

sergey-buturlakin commented 8 years ago

I thought that SignalR is a part of ASP.NET, probably I was wrong :) So in this case adding standalone package seems logical.

GetServices has to combine results from the base implementation because it contains a lot of infrastructure registrations like ITraceManager, IMessageBus, IStringMinifier, JsonSerializer and so on.

dotnetjunkie commented 8 years ago

I thought that SignalR is a part of ASP.NET, probably I was wrong :)

That's a fair point :). I will re-discuss this decision with my team.

tim-elvidge commented 8 years ago

Regarding this issue, hows this for a simple method to allow SimpleInjector creation of Hubs. In the startup class after the SimpleInjector container has been initialized:-

 public partial class Startup
   {
      public void Configuration(IAppBuilder app)
      {
         var container = App_Start.SimpleInjectorInitializer.Initialize();
         app.MapSignalR();
         var activator = new Hubs.DiHubActivator(container);
         GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
         GlobalHost.HubPipeline.RequireAuthentication();
      }
   }

and the activator class:-

   public class DiHubActivator : IHubActivator
   {
      private readonly Container container;

      public DiHubActivator(Container container)
      {
         this.container = container;
      }

      public IHub Create(HubDescriptor descriptor)
      {
         return (IHub) container.GetInstance(descriptor.HubType);
      }
   }
dotnetjunkie commented 8 years ago

I think the use of a custom IHubActivator is much more pleasant than implementing a custom dependency resolver, because the dependency resolver will be called for many internal SignalR dependencies that we don't care about. Typically, we only care about resolving hubs and that makes the hub activator an ideal interception point. In the end it all comes down a DI friendly framework with the proper abstractions for us to consume. IMO such IHubActivator is the proper abstraction.

sergey-buturlakin commented 8 years ago

It is a simple and obvious way but you will have no scope lifestyle support this is a big drawback IMO.

This was the first solution I tried, but I didn't succeed with scoped dependencies that I needed a lot.

dotnetjunkie commented 8 years ago

You still need the SimpleInjectorHubDispatcher if you want to do scoping, that's for sure. Unfortunately though there is a fundamental problem with the HubDispatcher base class and that is that when new OnXXX methods are added in a future version, it breaks existing derivatives, such as the SimpleInjectorHubDispatcher. It breaks, because all OnXXX methods need to be overridden. This is in fact a design problem in SignalR; there seems to be an abstraction missing that would prevent this from happening.

sergey-buturlakin commented 8 years ago

I'll check in the app, and I think mix should work fine.

sergey-buturlakin commented 8 years ago

I've checked (and remembered :)) that this doesn't work when custom dispatcher is passed.

app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", config);

this call tells SignalR to use custom dispatcher and the framework will try to resolve it using DependencyResolver, but the default resolver doesn't contain definition for it (indeed) and then Activator is used to create a dispatcher instance. SimpleInjectorHubDispatcher has no default constructor so the exception is thrown. So if the scope lifestyle needs to be supported then implementing only IHubActivator is not an option.

sergey-buturlakin commented 8 years ago

One change for the implementation I proposed

    public override object GetService(Type serviceType)
    {
        return base.GetService(serviceType) ?? _serviceProvider.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        var @base = base.GetServices(serviceType);

        var @this = (IEnumerable<object>) _serviceProvider.GetService(typeof (IEnumerable<>)
            .MakeGenericType(serviceType));

        return @base == null ? @this : @this == null ? @base : @base.Concat(@this);
    }

Look at first for base results then for registered in a container (this fixes a bug with custom registrations like config.Resolver.Register(typeof (JsonSerializer), () => serializer).

dotnetjunkie commented 8 years ago

And here we already see the DependenctResolver model break; what should we do when we want to override one of SignalR's default registrations in Simple Injector? If you fallback to Simple Injector, Simple Injector will never be queried in case SignalR has a default registration. This requires the registration to be made in SignalR's container, but that defeats the purpose of the custom DependencyResolver.

sergey-buturlakin commented 8 years ago

No model break here. You create an instance of custom Dependency resolver and operate it's methods. When you want to override registration then you write something like resolver.Register(typeof (SomeType), () => someTypeInstance). We could query Simple Injector container first (like it was originally), and then use registration like container.Register(someTypeInstance), but this approach has disadvantages:

tim-elvidge commented 8 years ago

Looking through the SignalR code I can see how the Owin extensions create the HubDispatcher:- https://github.com/SignalR/SignalR/blob/fa864e866bea4737dbd888f993892694f2f6b1a6/src/Microsoft.AspNet.SignalR.Core/Owin/Middleware/HubDispatcherMiddleware.cs

and how the Owin extensions are setup:- https://github.com/SignalR/SignalR/blob/dev/src/Microsoft.AspNet.SignalR.Core/Owin/OwinExtensions.cs

The main issue is that the Owin Middleware directly news up a HubDispatcher class, so even if there was a SimpleInjector SignalR dependency resolver you'd still be faced with the same issue that you couldn't inject your own HubDispatcher.

I hacked together a few classes that emulate what Owin does with the statement in Startup:- app.MapSignalR("/signalr", config);

and proved albeit in a quick hack that you can create your own OwinMiddleWare object that does successfuly use your SimpleInjectorHubDispatcher.

Let me know if this of interest?

dotnetjunkie commented 8 years ago

So what you are saying is that HubDispatcher is a non-sealed class with all virtual methods, but SignalR's integration packages make it extraordinary hard to replace it? Really nice.

I think having information about to create your own middleware is very interesting here; others might benefit from this as well.

tim-elvidge commented 8 years ago

To be fair to the SignalR team when I trolled through the source code a lot of it a few years old and I remember the wonder and coolness of SignalR all those years ago that it worked at all.

Just basically a proof of concept hack to get it working. Added it to an existing project where I'd already set up the activator via SimpleInjector and confirmed in debug mode that the container.BeginExecutionContextScope() was being invoked and that all other functions that did work continued to do so. so after adding three more classes the startup class now looks like:-

public partial class Startup
   {
      public void Configuration(IAppBuilder app)
      {
         var container = App_Start.SimpleInjectorInitializer.Initialize();
         //app.MapSignalR();
         var config = new HubConfiguration();
         app.MapMyDispatch(config, container);
         var activator = new Hubs.DiHubActivator(container);
         GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
         GlobalHost.HubPipeline.RequireAuthentication();
      }
   }

the MapMyDispatch uses the the IAppBuilder to inject the container aware HubDispatcher into the Owin pipeline.

When SignalR gets updated to the ASP.Net Core later this year, all of this will be hopefully redundant. DiHubDispatcherMiddleware.zip

dotnetjunkie commented 8 years ago

When SignalR gets updated to the ASP.Net Core later this year, all of this will be hopefully redundant.

That's why we are internally discussing whether we should even invest in supporting the current version of SignalR at all. Perhaps we should wait on RignalR Core. Still, this discussion can be very valuable to people who are trying to integrate with the current version.

sergey-buturlakin commented 8 years ago

The main issue is that the Owin Middleware directly news up a HubDispatcher class, so even if there was a SimpleInjector SignalR dependency resolver you'd still be faced with the same issue that you couldn't inject your own HubDispatcher.

I hacked together a few classes that emulate what Owin does with the statement in Startup:- app.MapSignalR("/signalr", config);

and proved albeit in a quick hack that you can create your own OwinMiddleWare object that does successfuly use your SimpleInjectorHubDispatcher.

This is not true and confuses others forcing them to think that all is complicated and not working!

When app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", config); is called SignalR doesn't use HubDispatcherMiddleware it uses PersistentConnectionMiddleware that creates the type you provided by template parameter (SimpleInjectorHubDispatcher in my case) using DependencyResolver.

https://github.com/SignalR/SignalR/blob/dev/src/Microsoft.AspNet.SignalR.Core/Owin/Middleware/PersistentConnectionMiddleware.cs

sergey-buturlakin commented 8 years ago

So no hacks and voodoo to be done to use own HubDispatcher.

tim-elvidge commented 8 years ago

Ok but I only went down this rabbit hole because I misunderstood you're early post about not being able to get an DI injected SimpleInjectorHubDispatcher.

sergey-buturlakin commented 8 years ago

Implementing DependencyResolver :) and passing it in a configuration object:

var config = new HubConfiguration
{
...
Resolver = new SimpleInjectorSignalRDependencyResolver(container)
}
tim-elvidge commented 8 years ago

Ok sergey, quite happy to remove my hack implementation but please post something that explains how you solved this issue in more detail, so that others can learn from your example.

sergey-buturlakin commented 8 years ago

If you don't use config object then you could just replace Resolver in GlobalHost GlobalHost.Resolver = new SimpleInjectorSignalRDependencyResolver(container)

tim-elvidge commented 8 years ago

So basically there are 2 solutions, create a SimpleInjectorSignalRDependencyResolver as you have shown earlier or build your own middleware as I have shown in a quick hack?

sergey-buturlakin commented 8 years ago

Ok sergey, quite happy to remove my hack implementation but please post something that explains how you solved this issue in more detail, so that other can learn from your example.

I'll try.

Developing own project I faced with need to use scoped dependencies. In WebApi with OWIN it is easy addressed by adding

app.Use(async (context, next) => {
    using (container.BeginExecutionContextScope())
        await next();
});

There is a simple and obvious way to implement IHubActivator, I did go this way and it almost works well in all type of SignalR transport but except WebSocket :( . It works for LongPolling well because each communication with server from client is a new request and the snipped above is called on each request so scopes are created and applied. But when WebSocket transport is used (that's basically the primary transport for SignalR IMO) only one request is created for client-server communication (besides negotiation) to establish websocket connection and after that request is completed and scope disposed. Each request to Hubs goes over an established connection and creates a new Hub instance for each request. If Hub has scoped dependency then the exception will be thrown.

The only way I found to provide scopes for all transport types was implementing a HubDispatcher descendant (SimpleInjectorHubDispatcher) to provide scope on each SignalR event that requires Hub creation.

The next task was to pass the dispatcher to SignalR. The framework provides builder.MapSignalR<T>(...) method to pass custom persistent connection implementation (default HubDispatcher is a descendant of PersistentConnection). In this case SignalR uses PersistentConnectionMiddleware as OWIN middleware, this middleware uses the T as type that is passed to PersistentConnectionFactory which creates instance of T using DependencyResolver or Activator (if resolver returns null). SimpleInjectorHubDispatcher has no parameterless constructor (it needs Container instance to create scopes) so there are two options to resolve this:

I think that the second option is better.

sergey-buturlakin commented 8 years ago

So basically there are 2 solutions, create a SimpleInjectorSignalRDependencyResolver as you have shown earlier or build your own middleware as I have shown in a quick hack?

If the hack works then it probably also is a solution :)

sergey-buturlakin commented 8 years ago

Hm... I found a funny mistake in my description, in fact only SimpleInjectorHubDispatcher activator has to be registered in the default resolver to get it working.

tim-elvidge commented 8 years ago

Ok sergey, Thanks for the clarification I don't really have an opinion one way or the other which is the preferred solution. My description of the code as a "hack" was more about it was copied from the original Microsoft Owin Extensions code and then chopped, commented out bits and generally hacked so as to provide a solution to the creation of a container aware HubDispatcher. But it's follows exactly the same path as the official Owin Extensions and should be just as robust. Really I just had some time up my sleeve and just wanted to provide a basis for a solution, any solution is ok, there's always going to be one more problem etc. tomorrow so moving on....

sergey-buturlakin commented 8 years ago

Tim it's OK, I also use such kind of problem solutions in some situations :)

Regarding the solution I provided, after I wrote the clarification I realized that it should work with SimpleInjectorSignalRDependencyResolver replaced by the custom IHubActivator implementation

var config = new HubConfiguration { };

var dispatcher = new SimpleInjectorHubDispatcher(container);
var activator = new SimpleInjectorHubActivator(container, config);

config.Resolver.Register(typeof(SimpleInjectorHubDispatcher), () => dispatcher);
config.Resolver.Register(typeof(IHubActivator), () => activator);

app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", config);
tim-elvidge commented 8 years ago

OK having tested your suggestion above I can confirm it works - without hacks, voodoo or anything else apart from the standard SignalR code so the Startup class will now look like:-

...
using SomeNameSpace.Hubs;

   public partial class Startup
   {
      public void Configuration(IAppBuilder app)
      {
         var container = App_Start.SimpleInjectorInitializer.Initialize();
         //app.MapSignalR();
         var config = new HubConfiguration();
         GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => new DiHubActivator(container));
         GlobalHost.DependencyResolver.Register(typeof(DiHubDispatcher), () => new DiHubDispatcher(container, config));
         GlobalHost.HubPipeline.RequireAuthentication(); //if required otherwise comment out
         app.MapSignalR<DiHubDispatcher>("/signalr", config);
      }
   }

DiHubDispatcher is

using System;
using System.Threading.Tasks;

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;
using SimpleInjector.Extensions.ExecutionContextScoping;

namespace SomeNameSpace.Hubs
{
   public class DiHubDispatcher : HubDispatcher
   {
      private readonly Container _container;
      public DiHubDispatcher(Container container, HubConfiguration configuration)
          : base(configuration)
      {
         _container = container;
      }

      protected override Task OnConnected(IRequest request, string connectionId) =>
         Invoke(() => base.OnConnected(request, connectionId));

      protected override Task OnReceived(IRequest request, string connectionId, string data) =>
         Invoke(() => base.OnReceived(request, connectionId, data));

      protected override Task OnDisconnected(IRequest request, string connectionId, bool stopCalled) =>
         Invoke(() => base.OnDisconnected(request, connectionId, stopCalled));

      protected override Task OnReconnected(IRequest request, string connectionId) =>
         Invoke(() => base.OnReconnected(request, connectionId));

      private async Task Invoke(Func<Task> method)
      {
         using (_container.BeginExecutionContextScope()) await method();
      }
   }
}

and DiHbActivator is

using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;

namespace SomeNameSpace.Hubs
{
   public class DiHubActivator : IHubActivator
   {
      private readonly Container container;
      public DiHubActivator(Container container) => this.container = container;
      public IHub Create(HubDescriptor descriptor) => (IHub) container.GetInstance(descriptor.HubType);
   }
}
sergey-buturlakin commented 8 years ago

So another option for the integration implementation is:

SimpleInjectorHubDispatcher .cs

using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;
using SimpleInjector.Extensions.ExecutionContextScoping;

namespace SimpleInjector.SignalR
{
    public class SimpleInjectorHubDispatcher : HubDispatcher
    {
        public SimpleInjectorHubDispatcher(Container container, HubConfiguration configuration) 
            : base(configuration)
        {
            _container = container;
        }

        protected override Task OnConnected(IRequest request, string connectionId)
        {
            return Invoke(() => base.OnConnected(request, connectionId));
        }

        protected override Task OnReceived(IRequest request, string connectionId, string data)
        {
            return Invoke(() => base.OnReceived(request, connectionId, data));
        }

        protected override Task OnDisconnected(IRequest request, string connectionId, 
            bool stopCalled)
        {
            return Invoke(() => base.OnDisconnected(request, connectionId, stopCalled));
        }

        protected override Task OnReconnected(IRequest request, string connectionId)
        {
            return Invoke(() => base.OnReconnected(request, connectionId));
        }

        private async Task Invoke(Func<Task> method)
        {
            using (_container.BeginExecutionContextScope())
                await method();
        }

        private readonly Container _container;
    }
}

SimpleInjectorHubActivator.cs

using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;

namespace SimpleInjector.SignalR
{
    public class SimpleInjectorHubActivator : IHubActivator
    {
        public SimpleInjectorHubActivator(Container container)
        {
            _container = container;
        }

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

        private readonly Container _container;
    }
}

SignalRExtensions.cs

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;

namespace SimpleInjector.SignalR
{
    public static class SignalRExtensions
    {
        public static void EnableSignalR(this Container container)
        {
            container.EnableSignalR(new HubConfiguration());
        }

        public static void EnableSignalR(this Container container, HubConfiguration config)
        {
            var hubActivator = new SimpleInjectorHubActivator(container);

            config.Resolver.Register(typeof(SimpleInjectorHubDispatcher), 
                () => new SimpleInjectorHubDispatcher(container, config));

            config.Resolver.Register(typeof(IHubActivator), () => hubActivator);
        }
    }
}

usage

var config = new HubConfiguration {};
container.EnableSignalR(config);
app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", config);

... or if you have no custom Hub config
container.EnableSignalR();
app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr");

If scoped lifestyle is not required then app.MapSignalR() could be called (without template parameter, e.g. app.MapSignalR("/signalr")).

What SimpleInjector team think about this integration implementation option?

inovykov commented 7 years ago

Hi Guys! I'm new to SignalR, so sorry in advance if I'm asking stupid things, but still: I try to employ approach suggested here but after I change

app.MapSignalR(config) 

to

 app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", config); 

my clientside hubs infrastructure stops working properly and client side callbacks are not triggered from server anymore

e.g.

var downloadFileToCustomerBrowserHub = $.connection.downloadFileToCustomerBrowserHub;
downloadFileToCustomerBrowserHub.client.downloadFileToCustomerBrowser = function (fileGuid) {
// this stopped working after I had changed registration type
}
$.connection.hub.start()

I still need this scoped approach, because I want dependencies to be disposed automatically on scope ends.

Can you please suggest how can I use hub approach (but not persisted connections) on client side with scoped lifetime for dependencies on backend?

camhart commented 6 years ago

@sergey-buturlakin I went with your last approach. My project uses Owin, MVC, WebApi, and SignalR.

I'm getting an ObjectDisposedException ({"Cannot access a disposed object.\r\nObject name: 'SimpleInjector.Scope'."}) in my SimpleInjectorHubActivator when attempting return (IHub)container.GetInstance(descriptor.HubType);.

Right now I have default scoped lifestyle set as follows:

            container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
                defaultLifestyle: new WebRequestLifestyle(), //mvc & webapi
                fallbackLifestyle: new AsyncScopedLifestyle() // for signalR?
            );

What's strange, is if I look up the stack trace one level, just before that ObjectDisposedException is thrown the following code is called from the SimpleInjectorHubDispatcher:

            using (AsyncScopedLifestyle.BeginScope(container))
            {
                await method();
            } 

Any ideas why I'm seeing that ObjectDisposedException? It's a pretty big project--so there's a lot going on (we need to break it apart). But if there's more context I can provide please just ask.

dotnetjunkie commented 6 years ago

Hi @camhart, please post a full stack trace.

camhart commented 6 years ago

@dotnetjunkie Here you go. Thanks for taking a look! I really appreciate it.

SimpleInjector.ActivationException
  HResult=0x80131500
  Message=Cannot access a disposed object.
Object name: 'SimpleInjector.Scope'.
  Source=SimpleInjector
  StackTrace:
   at SimpleInjector.InstanceProducer.GetInstance()
   at SimpleInjector.Container.GetInstance(Type serviceType)
   at Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubActivator.Create(HubDescriptor descriptor) in C:\Users\cam\projects\scs\Scs.Core.DependencyInjection.SimpleInjector\SimpleInjectorHubActivator.cs:line 23

Inner Exception 1:
ObjectDisposedException: Cannot access a disposed object.
Object name: 'SimpleInjector.Scope'.

Inner exception stack trace:

   at SimpleInjector.Scope.ThrowObjectDisposedException()
   at SimpleInjector.Scope.GetInstanceInternal[TImplementation](ScopedRegistration`1 registration)
   at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
   at lambda_method(Closure )
   at SimpleInjector.InstanceProducer.GetInstance()
dotnetjunkie commented 6 years ago

Hi @camhart, you only posted the stack trace of the inner most exception. Please post the stack trace up to the application's entry point.

camhart commented 6 years ago

@dotnetjunkie I can't seem to figure out how to get the full one... I was able to step out from the inner location and get a little more. I don't see any complete stack traces in my output. My exception settings are set to break when any Common Language Runtime Exceptions are thrown. I looked at the IIS Express logs and they don't show anything. When I look at the call stack when the debugger breaks once the exception is thrown, it lists 4 or 5 calls and then just shows an "External Code". I also changed code to catch the exception, but that doesn't appear to expose anymore information.

I've googled a fair bit, and am coming up empty handed. Here's what I found which has a bit more details, but it's still not what you're asking for (I don't think). Any pointers how to get the complete stack trace?

SimpleInjector.ActivationException
  HResult=0x80131500
  Message=Cannot access a disposed object.
Object name: 'SimpleInjector.Scope'.
  Source=SimpleInjector
  StackTrace:
   at SimpleInjector.InstanceProducer.GetInstance()
   at SimpleInjector.Container.GetInstance(Type serviceType)
   at Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubActivator.Create(HubDescriptor descriptor) in C:\Users\cam\projects\scs\Scs.Core.DependencyInjection.SimpleInjector\SimpleInjectorHubActivator.cs:line 23
   at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, StateChangeTracker tracker, Boolean throwIfFailedToCreate)
   at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.OnReceived(IRequest request, String connectionId, String data)
   at Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.<>n__1(IRequest request, String connectionId, String data)
   at Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.<>c__DisplayClass3_0.<OnReceived>b__0() in C:\Users\cam\projects\scs\Scs.Core.DependencyInjection.SimpleInjector\SimpleInjectorHubDispatcher.cs:line 27

Inner Exception 1:
ObjectDisposedException: Cannot access a disposed object.
Object name: 'SimpleInjector.Scope'.
camhart commented 6 years ago

I figured out from the call stack window I can right click on and have it show external code.... here's what that produced:

    SimpleInjector.dll!SimpleInjector.InstanceProducer.GetInstance()    Unknown
    SimpleInjector.dll!SimpleInjector.Container.GetInstance(System.Type serviceType)    Unknown
>   Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubActivator.Create(Microsoft.AspNet.SignalR.Hubs.HubDescriptor descriptor) Line 25   C#
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(Microsoft.AspNet.SignalR.IRequest request, Microsoft.AspNet.SignalR.Hubs.HubDescriptor descriptor, string connectionId, Microsoft.AspNet.SignalR.Hubs.StateChangeTracker tracker, bool throwIfFailedToCreate)   Unknown
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Hubs.HubDispatcher.OnReceived(Microsoft.AspNet.SignalR.IRequest request, string connectionId, string data)   Unknown
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.OnReceived.AnonymousMethod__0() Line 27 C#
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.Invoke(System.Func<System.Threading.Tasks.Task> method) Line 45 C#
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.OnReceived(Microsoft.AspNet.SignalR.IRequest request, string connectionId, string data) Line 27 C#
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequestPostGroupRead.AnonymousMethod__5()    Unknown
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(System.Func<System.Threading.Tasks.Task> func)    Unknown
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.WebSocketTransport.OnMessage(string message)  Unknown
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ProcessWebSocketRequestAsync(System.Net.WebSockets.WebSocket webSocket, System.Threading.CancellationToken disconnectToken, System.Func<object, System.Threading.Tasks.Task<Microsoft.AspNet.SignalR.WebSockets.WebSocketMessage>> messageRetriever, object state)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.OutputAsyncCausalityEvents.AnonymousMethod__0() Unknown
    mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__0()   Unknown
    mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.GetActionLogDelegate.AnonymousMethod__0()   Unknown
    System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action) Unknown
    mscorlib.dll!System.Threading.Tasks.Task.Execute()  Unknown
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot)    Unknown
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Unknown
    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown
    [Native to Managed Transition]  

Edit: If I ToString the exception, here's what I get:

"SimpleInjector.ActivationException: Cannot access a disposed object.
Object name: 'SimpleInjector.Scope'. ---> System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'SimpleInjector.Scope'.
   at SimpleInjector.Scope.ThrowObjectDisposedException()
   at SimpleInjector.Scope.GetInstanceInternal[TImplementation](ScopedRegistration`1 registration)
   at SimpleInjector.Advanced.Internal.LazyScopedRegistration`1.GetInstance(Scope scope)
   at lambda_method(Closure )
   at SimpleInjector.InstanceProducer.GetInstance()
   --- End of inner exception stack trace ---
   at SimpleInjector.InstanceProducer.GetInstance()
   at SimpleInjector.Container.GetInstance(Type serviceType)
   at Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubActivator.Create(HubDescriptor descriptor) in C:\\Users\\cam\\projects\\scs\\Scs.Core.DependencyInjection.SimpleInjector\\SimpleInjectorHubActivator.cs:line 30
   at Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(IRequest request, HubDescriptor descriptor, String connectionId, StateChangeTracker tracker, Boolean throwIfFailedToCreate)"
dotnetjunkie commented 6 years ago

I have no idea what's happening, but for some reason, your Scope instance is getting disposed, before you resolve instance. You have to add some tracing to find out where the scope is disposed.

For instance, you can add the following code to the SimpleInjectorHubDispatcher.Invoke method.

private async Task Invoke(Func<Task> method)
{
    using (var scope = _container.BeginExecutionContextScope())
    {
        scope.WhenScopeEnds(() =>
        {
            // Scope is disposed. Inspect the call stack to find out who is invoking dispose.
            Debugger.Break();
        });
        await method();
    }
}

On top of that, add a breakpoint to the SimpleInjectorHubActivator.Create method to figure out where it is called after Dispose is called.

camhart commented 6 years ago

What's strange is WhenScopeEnds appears to be called AFTER the exception occurs.

I do have web api controllers that resolve signalR hubs using a custom factory class that has a reference to IConnectionManager and they work fine (the hubs are used for outbound messaging). However I wonder if I'm breaking things there somehow...

camhart commented 6 years ago

@dotnetjunkie I only see the exception when OnReceived method is hit within SimpleInjectorHubDispatcher. OnConnected, OnReconnected, and OnDisconnected don't ever have issues.

scope.WhenScopeEnds is being triggered after the exception... now if there is some event loop or something that's simply delaying the execution until after the current executing code is done, I don't know. But it certainly appears as though the exception gets thrown causing execution to pass the using statement and then WhenScopeEnds is triggered.

Here's the callstack from within "WhenScopeEnds" when an OnReceived method was invoked and the exception seen:

>   Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.Invoke.AnonymousMethod__6_0() Line 50   C#  Symbols loaded.
    SimpleInjector.dll!SimpleInjector.Scope.ExecuteAllRegisteredEndScopeActions()   Unknown Non-user code. Skipped loading symbols.
    SimpleInjector.dll!SimpleInjector.Scope.DisposeRecursively(bool operatingInException = false)   Unknown Non-user code. Skipped loading symbols.
    SimpleInjector.dll!SimpleInjector.Scope.Dispose(bool disposing) Unknown Non-user code. Skipped loading symbols.
    SimpleInjector.dll!SimpleInjector.Scope.Dispose()   Unknown Non-user code. Skipped loading symbols.
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.Invoke(System.Func<System.Threading.Tasks.Task> method = {Method = {System.Reflection.RuntimeMethodInfo}}) Line 54  C#  Symbols loaded.
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.OnReceived(Microsoft.AspNet.SignalR.IRequest request = {Microsoft.AspNet.SignalR.Owin.ServerRequest}, string connectionId = "37c3e877-c276-45e6-9ef8-cb1db2676bd3", string data = "{\"H\":\"casehub\",\"M\":\"Init\",\"A\":[\"9035\"],\"I\":0}") Line 27    C#  Symbols loaded.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequestPostGroupRead.AnonymousMethod__5()    Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(System.Func<System.Threading.Tasks.Task> func)    Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.WebSocketTransport.OnMessage(string message)  Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ProcessWebSocketRequestAsync(System.Net.WebSockets.WebSocket webSocket = {System.Web.WebSockets.AspNetWebSocket}, System.Threading.CancellationToken disconnectToken = IsCancellationRequested = false, System.Func<object, System.Threading.Tasks.Task<Microsoft.AspNet.SignalR.WebSockets.WebSocketMessage>> messageRetriever = {Method = {System.Reflection.RuntimeMethodInfo}}, object state = {Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ReceiveContext})    Unknown Non-user code. Skipped loading symbols.

    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.OutputAsyncCausalityEvents.AnonymousMethod__0() Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__0()   Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.GetActionLogDelegate.AnonymousMethod__0()   Unknown Non-user code. Skipped loading symbols.
    System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action) Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.Tasks.Task.Execute()  Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot = Id = 9067, Status = Running, Method = "Void <QueueAsynchronous>b__0(System.Threading.Tasks.Task)")    Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution) Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown Non-user code. Skipped loading symbols.
    [Native to Managed Transition]      Annotated Frame

Here's the callstack from within "WhenScopeEnds" when an OnDisconnected method was invoked and no exception occured:

>   Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.Invoke.AnonymousMethod__6_0() Line 50   C#  Symbols loaded.
    SimpleInjector.dll!SimpleInjector.Scope.ExecuteAllRegisteredEndScopeActions()   Unknown Non-user code. Skipped loading symbols.
    SimpleInjector.dll!SimpleInjector.Scope.DisposeRecursively(bool operatingInException = false)   Unknown Non-user code. Skipped loading symbols.
    SimpleInjector.dll!SimpleInjector.Scope.Dispose(bool disposing) Unknown Non-user code. Skipped loading symbols.
    SimpleInjector.dll!SimpleInjector.Scope.Dispose()   Unknown Non-user code. Skipped loading symbols.
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.Invoke(System.Func<System.Threading.Tasks.Task> method = {Method = {System.Reflection.RuntimeMethodInfo}}) Line 54  C#  Symbols loaded.
    Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.OnDisconnected(Microsoft.AspNet.SignalR.IRequest request = {Microsoft.AspNet.SignalR.Owin.ServerRequest}, string connectionId = "37c3e877-c276-45e6-9ef8-cb1db2676bd3", bool stopCalled = false) Line 33    C#  Symbols loaded.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequestPostGroupRead.AnonymousMethod__7()    Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(System.Func<System.Threading.Tasks.Task> func)    Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.TransportDisconnectBase.Abort(bool clean) Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.CheckDisconnect(Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.ConnectionMetadata metadata)    Unknown Non-user code. Skipped loading symbols.
    Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.Beat(object state) Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown Non-user code. Skipped loading symbols.

    mscorlib.dll!System.Threading.TimerQueueTimer.CallCallback()    Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.TimerQueueTimer.Fire()    Unknown Non-user code. Skipped loading symbols.
    mscorlib.dll!System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()  Unknown Non-user code. Skipped loading symbols.

    mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown Non-user code. Skipped loading symbols.
    [Native to Managed Transition]      Annotated Frame
camhart commented 6 years ago

@sergey-buturlakin @dotnetjunkie Just FYI, in order for IConnectionManager.GetHubContext<SomeHub>() to work, you need to provide a SimpleInjectorSignalRDependencyResolver (just like the one @sergey-buturlakin first referenced). Otherwise there are no errors, but attempting to send anything to the clients silently fails.

Edit: This is in addition to all the stuff @sergey-buturlakin amazingly put together in this comment

dotnetjunkie commented 6 years ago

We decided not to create and maintain a SignalR package.

oferns commented 6 years ago

Hi. I was reading this wondering how to get Simple Injector to work with SignalR in dot .NET Core 2.1. I want to post my solution to a) see if there is anything I have got completely wrong, and if not then b) share it with the community. Maybe I have missed some post somewhere so apologies if I am reinventing the wheel. Its one class that inherits from IHubActivator.

using System;
using Microsoft.AspNetCore.SignalR;
using SimpleInjector;

public sealed class SimpleInjectorHubActivator<T> : IHubActivator<T> where T : Hub {
    private readonly Container container;

    public SimpleInjectorHubActivator(Container container) {
        this.container = container ?? throw new ArgumentNullException(nameof(container));
    }

    public T Create() => container.GetInstance<T>();

    public void Release(T hub) {
        // Simple Injector  takes care of this
    }
}

Then in StartUp.cs (where container is the SI container) add SignalR as usual

services.AddSignalR();

And register your hubs as usual

app.UseSignalR(routes => {
    routes.MapHub<ChatHub>("/chathub");
});

swap out the hub activator for the SimpleInjector activator

services.AddSingleton(container);
services.AddSingleton(typeof(IHubActivator<>), typeof(SimpleInjectorHubActivator<>));

And finally register your hubs

foreach (Type type in container.GetTypesToRegister(typeof(Hub), typeof(ChatHub).Assembly))
{
    container.Register(type, type, Lifestyle.Scoped);
}

This then allows me to inject my services as I would in a Controller or whatever

It works but I would like to hear thoughts.

dotnetjunkie commented 6 years ago

Hi @oferns,

There are unfortunately a few quirks in SignalR that make integrating more difficult that otherwise would be, e.g.

That said, I made some adjustments to your code to simplify the definition of the hub activator, its registration, and added auto-registration of hub types.

This code has my seal of approval :)

oferns commented 6 years ago

Hi @dotnetjunkie Thanks for getting back to me. Ive implemented the changes but SI doesnt like this line...

container.Register(type, Lifestyle.Scoped);

Should it just be

container.Register(type);

O

dotnetjunkie commented 6 years ago

Woops! I'm sorry, this should be:

container.Register(type, type, Lifestyle.Scoped);

You'll need to register your hubs as Scoped to prevent Simple Injector from warning about disposable transients. To prevent this, you would have to suppress this warning, which is probably more work than registering it as Scoped.

I updated your response to reflect this.

oferns commented 6 years ago

Brilliant. Thanks. Will Simple Injector call dispose on these transient hubs? Or is it unnecessary?

dotnetjunkie commented 6 years ago

Will Simple Injector call dispose on these transient hubs?

Only when a Hub is registered as Scoped.

Or is it unnecessary?

It is unnecessary in case your derived class does not override Dispose(bool), because the base implementation does not dispose anything.

NimaAra commented 6 years ago

@dotnetjunkie Unfortunately this solution throws an exception when the HttpTransportType is set to LongPolling. I have opened https://github.com/simpleinjector/SimpleInjector/issues/630.