-
Notifications
You must be signed in to change notification settings - Fork 152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SignalR integration #232
Comments
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 I've got one question about your implementation though. Currently, your |
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. 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 |
It is a simple and obvious way but you will have no scope lifestyle support This was the first solution I tried, but I didn't succeed with scoped |
You still need the |
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.
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. |
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 |
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
|
Looking through the SignalR code I can see how the Owin extensions create the HubDispatcher:- and how the Owin extensions are setup:- 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:- 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. 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. |
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. |
This is not true and confuses others forcing them to think that all is complicated and not working! When |
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:
|
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 |
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? |
I'll try. Developing own project I faced with need to use scoped dependencies. In WebApi with OWIN it is easy addressed by adding
There is a simple and obvious way to implement The only way I found to provide scopes for all transport types was implementing a The next task was to pass the dispatcher to SignalR. The framework provides
I think that the second option is better. |
If the hack works then it probably also is a solution :) |
Hm... I found a funny mistake in my description, in fact only |
Ok sergey, |
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 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 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: 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 ( Right now I have default scoped lifestyle set as follows:
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:
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.
Inner exception stack trace:
|
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?
|
I figured out from the call stack window I can right click on and have it show external code.... here's what that produced:
Edit: If I ToString the exception, here's what I get:
|
I have no idea what's happening, but for some reason, your For instance, you can add the following code to the 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 |
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:
Here's the callstack from within "WhenScopeEnds" when an OnDisconnected method was invoked and no exception occured:
|
@sergey-buturlakin @dotnetjunkie 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.
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
Should it just be
O |
Woops! I'm sorry, this should be: container.Register(type, type, Lifestyle.Scoped); You'll need to register your hubs as I updated your response to reflect this. |
Brilliant. Thanks. Will Simple Injector call dispose on these transient hubs? Or is it unnecessary? |
Only when a Hub is registered as
It is unnecessary in case your derived class does not override |
@dotnetjunkie Unfortunately this solution throws an exception when the |
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.
The text was updated successfully, but these errors were encountered: