Closed sergey-buturlakin closed 6 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.
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.
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.
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);
}
}
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.
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.
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.
I'll check in the app, and I think mix should work fine.
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.
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
).
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.
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:
config.Resolver.Register(typeof (JsonSerializer), () => serializer)
or GlobalHost.DependencyResolver.Register(typeof (JsonSerializer), () => serializer)`. So moving overriding to container will confuse developers;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
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?
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.
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
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.
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.
So no hacks and voodoo to be done to use own HubDispatcher.
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.
Implementing DependencyResolver :) and passing it in a configuration object:
var config = new HubConfiguration
{
...
Resolver = new SimpleInjectorSignalRDependencyResolver(container)
}
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.
If you don't use config object then you could just replace Resolver in GlobalHost
GlobalHost.Resolver = new SimpleInjectorSignalRDependencyResolver(container)
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?
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:
GlobalHost.Resolver
or hubConfiguration.Resolver
(config.Resolver.Register(typeof (ChatHub), () => container.GetInstance<ChatHub>())
);SimpleInjectorSignalRDependencyResolver
).I think that the second option is better.
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 :)
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.
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....
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);
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);
}
}
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?
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?
@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.
Hi @camhart, please post a full stack trace.
@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()
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.
@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'.
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)"
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.
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...
@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
@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
We decided not to create and maintain a SignalR package.
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.
Hi @oferns,
There are unfortunately a few quirks in SignalR that make integrating more difficult that otherwise would be, e.g.
Hub
base class implements IDisposable
, which is a design error. A similar issue exists with ASP.NET Core MVC controllers, and Microsoft admitted that the sole reason for doing so was backwards compatibility.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 :)
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
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.
Brilliant. Thanks. Will Simple Injector call dispose on these transient hubs? Or is it unnecessary?
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.
@dotnetjunkie Unfortunately this solution throws an exception when the HttpTransportType
is set to LongPolling. I have opened https://github.com/simpleinjector/SimpleInjector/issues/630.
Could you add an integration package for SignalR using the following source code?
file SimpleInjectorSignalRDependencyResolver.cs
file SimpleInjectorHubDispatcher.cs
Registration sample:
P.S. I tried to make a PR but experienced problems with the project solution in my dev environment.