-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Signalr internal types should continue to remain public in 3.0 until alternative pipeline is created in release 5. This is a breaking change from 2.2 #14247
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
Comments
If we do such a thing it will be in 5.0, so I'm not sure what the value will be since the pipeline is planned for that release. Certainly if we don't get a full hub pipeline, we can do this as a fallback. Types in the internal namespace are unsupported and are not protected from breaking changes. Can you provide example code of what you were doing so we can help guide to you an appropriate workaround for 3.0? |
Linking #5353 for reference (the Hub Pipeline issue) |
I'm having a similar experience as @brijeshb - we also override the HubDispatcher as it seems to be the only way to get some access to the pipeline. We need to start a IoC scope for each message coming in over SignalR to ensure DB changes are committed upon success and rolled back upon failures. This is something already incorporated in our IoC setup for normal web requests, and I don't want to solve the same problem twice just because of this. I just converted our solution to Core 3.0, but at the moment this is the last blocker, and right now it means we can't convert and enjoy the nice features available in Core 3.0 :( I get your point about the classes being internal - but there isn't a "right" way to do these things, as I can see - and the "wrong" way is pretty OK. And if a permanent solution isn't planned until 5.0 I really hope you help us out until something better is available ... |
I presume you're talking about a separate container from the ASP.NET Core DI system? Because we already create a DI scope for each message in that system. |
@anurse I've got similar problem: I've used custom subclass of |
You could use a custom I think this is precisely the kind of reason we need #5353. Relying on internal types to do this kind of thing isn't really sustainable. The Dispatcher (being in an |
Thank you!
Yes, it seems too low-level.
That's, of course, the most straightforward solution, but it introduces a lot of boilerplate code (especially when you have to validate a bunch of parameters). Dispatcher allowed to use generalized mechanism to validate all parameters in attribute-driven way, for example.
+1, officially supported and documented pipline is really desired |
Running into the same issue, using CustomHubDispatcher derived from DefaultHubDispatcher and overriding DispatchMessageAsync to do some cross cutting concerns. This changes forces me to spread code to hub methods which is not necessary/ideal. Looking forward to a better alternative. |
The integration of non-conforming containers, such as Simple Injector, Castle Windsor, and Ninject, depend on the existence of good interception points, such an Take for instance on how Simple Injector users are expected to integrate with SignalR Core 2: simpleinjector/SimpleInjector#631. This is now broken with SignalR Core 3. There seems a more general issue here that transcends SignalR. It seems every new framework that's coming out of the .NET Core stack misses these required abstraction. Some of the frameworks I engaged in discussions about these missing abstractions are: Azure Functions, gRPC, Blazer Components, and now SignalR Core. This seems a recurring pattern. It seems to me that some common (internal) Microsoft guidelines are missing about how to design libraries and frameworks on top of .NET Core. /cc @davidfowl |
I’m not sure why you needed a hub dispatcher in the first place but I do see a bigger need to support decorators in the default container. |
David, you are ignoring my bigger point here: the need to have good interception points. This goes beyond DI. You, as architect, should be pushing this message, to make the platform as extendable and usable as possible for anyone. |
The system is fundamentally based on DI, non-conforming containers plug into the existing system using the built in DI. Before the solution you proposed was using inheritance as a workaround to do decoration around the default implementation. Decoration would allow this in a more elegant way. As for blazor, there’s work to be done there.
We try to apply the same patterns across the stack for activation (grpc has a similar pattern to signalr). Sometimes we miss things though and it doesn’t help when we get late feedback outside of the development cycle. It makes it harder to react to changes that we made that in turn might be breaking again (we’d like to reduce the churn). What makes it even harder is when the intended extensibility points aren’t being used but unintended ones are (like the above). |
Hi @davidfowl, It would indeed be beneficial to see decoration been added as first-class citizen to the built-in Container, that's for sure. That would certainly have made the integration I proposed for integrating Simple Injector with SignalR Core 2 (a bit) "more elegant." But although an improvement, this doesn't solve the problem we have now. You seem to mention that SignalR has "intended extensibility points [that] aren’t being used." Please help me out here. Which extensibility points currently exist in SignalR Core 3 that allow Simple Injector users, users of other non-conforming containers, and other users that which to intercept the creation of SignalR types? You would be of great help if you could guide anyone reading this in how to plug in to SignalR Core 3 atm.
Yes; sometimes developers miss things. That happens. I don't blame them. My earlier comment wasn't meant as an attack on those developers. My point, however, merely was that I think that these recurring extensibility issues can be prevented elegantly by having some good guidance in place that teaches Microsoft (and third-party) developers, that they must "make sure that there are adequate composition roots per framework to make integration as smooth as it can be." I hope this makes sense. |
It would make lots of things easier and that’s a better solution for “hooking into” the hub dispatcher. All of the above implementations are trying to decorate in some form (otherwise it would just break how the system works). I don’t understand why it isn’t sufficient to create the scope in the activator itself and destroy it in dispose. What doesn’t work there? |
David, Thank you for your input. The custom Can you confirm whether all Hub operations are always called from within a normal ASP.NET Core pipeline or not? If so, integration for non-conformers is actually pretty easy, because it only concerns replacing the default hub activator. This might not hold for other scenarios, but for logging and applying other cross-cutting concerns, the same problems would exist for MVC Controllers, which is something that would normally be achieved using middleware (or using a more SOLID design internally). Thanks in advance |
That's not a great assumption, especially for things like long polling and non-http SignalR. That said, I don't understand why you can't create your scope and tear it down in CreateHub and Release. What am I missing? |
Hmmm… that possibly complicates things… a lot.
Although that would work in some cases, that would cause trouble when the hub is running inside a HTTP request, because in that case Simple Injector has wrapped the request with middleware (via an IStartupFilter) in a (lifetime) scope. Destroying the scope at that point might cause any other middleware that the user might have applied, to fail. On the other hand, wrapping the I can see, however, a hybrid model, where the custom hub activator makes use of an active scope. In absence of such scope, the hub can manage its own scope by destroying it inside the I tested whether SignalR resolves the activator on each request and this seems to be the case, but I want to double check with you if this is stable behavior on which anyone can safely depend. If, in a future release, SignalR would resolve a hub activator only once (and caches it), that would then break such integration. Can you confirm that resolving the hub activator on each request is intended and you have the proper verification mechanisms (i.e. unit tests and documentation) in place that secures this behavior? |
What happens if you create a new scope if a scope already exists in SimpleInjector?
I'm not sure it's reasonable to rely on this behavior. Long polling for example will end up with hub execution between requests and the Azure SignalR integration runs completely outside of the http requests (though that should be easier).
It's resolved once per hub activation (so multiple times within a request). OnConnected, hub invocation and OnDisconnected. |
Let me start with apologizing to the OP for accidentally high jacking this discussion. I didn't intend to do so, but hopefully this discussion leads to something for everyone.
The behavior wouldn't be different from that of MS.DI (except for the ambient model of course), because a nested scope behaves as its own isolated bubble. Nothing is shared between the bubbles. This is obviously the behavior you want, but creating new scopes implicitly on the background could obviously be confusing to the user. Unless the users do so themselves explicitly, you don't want a single web request to run in more than one scope.
Perhaps I should show an example? This might give a better understanding of how I think I can solve this problem. Please supply me with feedback on whether this could work, or wouldn't: // Must be registered using AddScoped
public sealed class SimpleInjectorHubActivator<T> : IHubActivator<T> where T : Hub
{
private static AsyncScopedLifestyle ScopedLifestyle = new AsyncScopedLifestyle();
private readonly Container container;
private readonly IServiceProviderAccessor accessor;
// This impl must be scoped because of these two fields.
// IServiceProvider must be the request-scoped provider, not the root.
private readonly IServiceProvider msScope;
private Scope ownedScope;
public SimpleInjectorHubActivator(
Container container,
SimpleInjector.Integration.ServiceCollection.IServiceProviderAccessor accessor,
IServiceProvider msScope)
{
this.container = container;
this.accessor = accessor;
this.msScope = msScope;
}
public T Create()
{
StartScope();
// Resolve the Hub; this might cause dependencies to be loaded from MS.DI.
return this.container.GetInstance<T>();
}
public void Release(T hub)
{
EndScope();
}
private void StartScope()
{
if (!this.IsRunningInRequestScope)
{
// Create a new ambient scope. This relies on AsyncLocal<T>
this.ownedScope = AsyncScopedLifestyle.BeginScope(this.container);
// Supply the scoped IServiceProvider to Simple Injector's integration
// to allow Simple Injector to cross-wire from the curent msScope.
// This uses an AsyncLocal<IServiceProvider> to flow the scope.
this.accessor.SetCurrent(this.msScope);
}
}
private bool IsRunningInRequestScope =>
ScopedLifestyle.GetCurrentScope(this.container) != null;
private void EndScope()
{
this.ownedScope?.Dispose();
}
} The following points describe what this activator does:
This implementation makes a few assumptions:
Can you feedback on this implementation and the assumptions I'm am making with this implementation? |
I would remove this check completely.
👍
👍
👍 |
Thank you for your feedback
Would you mind elaborating on this? Why would you prefer removing this? Is this because of possible issues, or something else? Why wouldn't you mind having a nested scope? Or are you suggesting something else? |
It's broken if you end up using the existing request scope. The request could end immediately after the check for |
But if |
Sorry I meant returns true not false.
Yes. The HTTP layer in SignalR is treated like a transport and we just transfer bytes into the pipe, we don't do invocation of the operation on the request itself. The reason it can happy though is because we don't explicitly clear the execution context before calling into the logic that eventually runs the hub. That logic can start from the request thread and execute in parallel with it. It's decoupled because of other transports decoupled from HTTP. The execution context behavior is mostly a side effect of it not being explicitly cleared. |
Thank you @davidfowl for this valuable information. This means that the activator must always create a new scope for creating a hub. I now have enough information to be able to create an integration package. |
Thank you for contacting us. Due to a lack of activity on this discussion issue we're closing it in an effort to keep our backlog clean. If you believe there is a concern related to the ASP.NET Core framework, which hasn't been addressed yet, please file a new issue. This issue will be locked after 30 more days of inactivity. If you still wish to discuss this subject after then, please create a new issue! |
Is your feature request related to a problem? Please describe.
Several internal classes which are key to customizing the signalr pipeline and message processing were made internal in 3.0. These were public in 2.2 and although they were internal classes, they exposed functionality that advanced scenarios / programmers could make use of to build enterprise grade apps. This breaking change which did not introduce an alternative approach forces developers to stay on 2.2.
Describe the solution you'd like
Please revert the conversion of these pubternal->internal types so they continue to stay pubternal until an alternative approach is developed.
Additional context
Signalr 's pipeline is not as mature as the regular asp.net mvc pipeline. It's currently a pain to implement advanced features that leads to very verbose code. This is a known problem and there is work being tracked for the next major release.
Before 3.0 as long as the core dependencies could be overridden. One could always copy the default implementations and override them with custom behavior.
In 3 these have been made internal without exposing an alternative solution. This leaves anyone hoping to make advanced use of signalr stuck on 2.2 until the signalr pipeline eventually rolls out in the next 3-6 months.
Some example use cases:
We override the hub dispatcher to implement better logging as a true cross cutting concern, without needing to sprinkle it throughout the hub methods.
We also perform global exception handling here enabling the transformation of lower level exceptions into well formed error responses, which are more useful than the standard hubexception.
One of the advanced use cases actually involves us using the hubdispatcher as a middleware to build a websocket proxy. We use a hubconnection connected to backend resources and replay the deserialized messages onto the correct backend resource. Without the ability to support generics at the hub dispatcher level, we would need to implement every method in the hub as well.
It's understandable that signalr's implementation has a long way to go before more of these features can be provided out of the box, and in some cases because of the need to support several protocols , it may be impossible to implement these in the platform. But atleast exposing the hooks allows consumers to override behavior and handle specific scenarios.
I have a humble request that we don't leave developers high and dry, by taking away functionality without introducing a sensible alternative first.
The text was updated successfully, but these errors were encountered: