We had https://github.com/aspnet/SignalR/issues/871 and https://github.com/aspnet/SignalR/issues/924 tracking some of this, and resolved those by adding HubDispatcher as a user-overridable abstraction. However, there's still value in the simple abstraction Hub Pipeline modules provided in ASP.NET SignalR. We should consider bringing that abstraction back.
Considerations:
Will hub pipeline be added to version 3? i think it's an essential feature for enterprise applications
Please let us know if this proposal will work with your scenarios or not.
These are the core scenarios that users have asked for in GitHub issues:
Example of someones usage in ASP.NET SignalR 2.X:
```c#
public class IsConnectedPipeLine : HubPipelineModule
{
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
if (context.MethodDescriptor.Name == "GetToken")
return true;
return ChatIdentity.CheckToken(context.Hub.Context.GetCurrentUserToken());
}
}
With the below proposal:
```c#
public class IsConnectedFilter : IHubFilter
{
public ValueTask<object> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
{
if (invocationContext.HubMethod.Name == "GetToken" ||
ChatIdentity.CheckToken(context.Context.GetCurrentUserToken()))
{
return next(invocationContext);
}
}
}
We will provide a single method for incoming invocations (client to server).
And two methods to provide hooks for a connection connecting and disconnecting.
```c#
public interface IHubFilter
{
ValueTask
For registration we will have two types. Global filter registration and per-hub filter registration.
Global filters will run first (and exit last since it's middleware) and per-hub filters will run after (and exit first).
For DI activated IHubFilter's they will have the same scope as the Hub; a new one per method invocation.
For perf it is recommended to either provide an instance or register the type as a singleton (services.AddSingleton<GlobalFilter>()), assuming it doesn't need objects from DI.
```c#
services.AddSignalR(options =>
{
// registration order matters
options.AddFilter<GlobalFilter>();
options.AddFilter<SecondFilter>();
options.AddFilter(new CustomFilter());
}).AddHubOptions<THub>(options =>
{
// registration order matters
options.AddFilter<RunFirstFilter>();
options.AddFilter<RunSecondFilter>();
});
Examples:
```c#
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
class StreamingMethodAttribute : Attribute
{
}
class TestHub : Hub
{
[StreamingMethod]
IAsyncEnumerable
{
// ...
}
}
class CustomFilter : IHubFilter
{
async ValueTask
if (Attribute.IsDefined(invocationContext.HubMethod, typeof(StreamingMethod)))
{
// change return value of a streaming method!
return Add((IAsyncEnumerable<int>)res);
async IAsyncEnumerable<int> Add(IAsyncEnumerable<int> enumerable)
{
await foreach(var item in enumerable)
{
yield return item + 5;
}
}
}
return res;
}
catch
{
throw new HubException("some error");
}
finally
{
// logging
}
}
}
```
Please let us know if this proposal will work with your scenarios or not.
One question we have is:
Thanks for following up on this! 馃榿
Please let us know if this proposal will work with your scenarios or not.
Looks good. I think that proposal will be good for what I want to do. In MVC, I use middleware to setup services for the lifetime of the request with things like the client's address, the details of the user, the culture to use, and so on. A rough example is this:
```c#
public async Task Invoke(HttpContext httpContext) {
var addressManager = httpContext.RequestServices.GetRequiredService
using (var token = addressManager.SetCurrentAddress(GetAddressFromRequest(httpContext.Request))) {
await _next(httpContext);
}
// Note: Disposing the token will "unregister" the current address.
}
The `IAddressManager` can then be used by any other services during that request to get the address that the request came from. That's useful for things like auditing database changes, and so on, and means the database auditing doesn't need to reference any of the HTTP libraries.
It _looks_ like I can do the same thing with the proposed SignalR filters. Am I correct in saying this would be the equivalent filter code?
```c#
public async ValueTask<object> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object>> next)
{
var addressManager = invocationContext.ServiceProvider.GetRequiredService<IAddressManager>;
using (var token = addressManager.SetCurrentAddress(GetAddressFromInvocation(invocationContext))) {
await next(invocationContext);
}
}
Do users want outgoing hooks (server to client)?
Personally, I haven't encountered a need for that.
@reduckted Funny, this is what we plan to do with IHttpContextAccessor to make it work properly for SignalR 馃槃
When is the new version released? :D
It will be in 5.0.0-preview6
Most helpful comment
Please let us know if this proposal will work with your scenarios or not.
HubFilter
User Scenarios
These are the core scenarios that users have asked for in GitHub issues:
Example of someones usage in ASP.NET SignalR 2.X:
```c#
public class IsConnectedPipeLine : HubPipelineModule
{
protected override bool OnBeforeIncoming(IHubIncomingInvokerContext context)
{
if (context.MethodDescriptor.Name == "GetToken")
return true;
return ChatIdentity.CheckToken(context.Hub.Context.GetCurrentUserToken());
}
}
Proposal
We will provide a single method for incoming invocations (client to server).
And two methods to provide hooks for a connection connecting and disconnecting.
```c#
public interface IHubFilter
{
ValueTask
Examples:
```c#
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
class StreamingMethodAttribute : Attribute
{
}
class TestHub : Hub Stream()
{
[StreamingMethod]
IAsyncEnumerable
{
// ...
}
}
class CustomFilter : IHubFilter
{
async ValueTask
}
```
Modifications
Provide Feedback
Please let us know if this proposal will work with your scenarios or not.
One question we have is: