.net core signalR 全局异常处理

Hub的异常拦截

environment

.net core 5.0

主题

对于hub中的方法执行 实现一个全局的异常处理

食用方法

1.实现自定义拦截类:

Microsoft.AspNetCore.SignalR.IHubFilter public class HubMethodFilter : IHubFilter {

    public async ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next)
    {
        try
        {
            return await next(invocationContext);
        }
        catch(Exception e)
        {
            do something
            // write log,send notice....
        }
    }

}

2.注册拦截器

services.AddSignalR(hubOptions =>
{
    hubOptions.AddFilter<HubMethodFilter>();
})

扩展

在自定义拦截类中可使用 .net core 的依赖注入

文档

官方文档

可忽略的源码

扩展

首先从注册的地方查找:Microsoft.Extensions.DependencyInjection.SignalRDependencyInjectionExtensions:

public static ISignalRServerBuilder AddSignalR(this IServiceCollection services, Action<HubOptions> configure)
{
    //IL_0008: Unknown result type (might be due to invalid IL or missing references)
    if (services == null)
    {
        throw new ArgumentNullException("services");
    }
    ISignalRServerBuilder result = services.AddSignalR();
    services.Configure<HubOptions>(configure);
    return result;
}

public static ISignalRServerBuilder AddSignalR(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException("services");
}
services.AddConnections();
services.Configure(delegate(WebSocketOptions o)
{
o.KeepAliveInterval = TimeSpan.Zero;
});
services.TryAddSingleton<SignalRMarkerService>();
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<HubOptions>, HubOptionsSetup>());
return services.AddSignalRCore();
}

public static ISignalRServerBuilder AddSignalRCore(this IServiceCollection services)
{
services.TryAddSingleton<SignalRCoreMarkerService>();
services.TryAddSingleton(typeof(HubLifetimeManager<>), typeof(DefaultHubLifetimeManager<>));
services.TryAddSingleton(typeof(IHubProtocolResolver), typeof(DefaultHubProtocolResolver));
services.TryAddSingleton(typeof(IHubContext<>), typeof(HubContext<>));
services.TryAddSingleton(typeof(IHubContext<, >), typeof(HubContext<, >));
services.TryAddSingleton(typeof(HubConnectionHandler<>), typeof(HubConnectionHandler<>));
services.TryAddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
services.TryAddSingleton(typeof(HubDispatcher<>), typeof(DefaultHubDispatcher<>));
services.TryAddScoped(typeof(IHubActivator<>), typeof(DefaultHubActivator<>));
services.AddAuthorization();
SignalRServerBuilder signalRServerBuilder = new SignalRServerBuilder(services);
signalRServerBuilder.AddJsonProtocol();
return signalRServerBuilder;
}

看完后你就能发现,在默认注册的类中,没有一个是符合需要的其中HubDispatcher<>最为迷惑,里面有提供异常处理,但类是internal的,只好当场离去

既然默认配置都没有,只好从参数上上手还好就一个参数,找到其对应的扩展类Microsoft.AspNetCore.SignalR.HubOptionsExtensions:

ps:你可能会问为啥直接找扩展类,->HubOptions类的公开属性你看一下就能明白了

/// <summary>
/// Methods to add <see cref="IHubFilter"/>'s to Hubs.
/// </summary>
public static class HubOptionsExtensions
{
    /// <summary>
    /// Adds an instance of an <see cref="IHubFilter"/> to the <see cref="HubOptions"/>.
    /// </summary>
    /// <param name="options">The options to add a filter to.</param>
    /// <param name="hubFilter">The filter instance to add to the options.</param>
    public static void AddFilter(this HubOptions options, IHubFilter hubFilter)
    {
        _ = options ?? throw new ArgumentNullException(nameof(options));
        _ = hubFilter ?? throw new ArgumentNullException(nameof(hubFilter));
    if (options.HubFilters == null)
    {
        options.HubFilters = new List&lt;IHubFilter&gt;();
    }

    options.HubFilters.Add(hubFilter);
}

/// &lt;summary&gt;
/// Adds an &lt;see cref="IHubFilter"/&gt; type to the &lt;see cref="HubOptions"/&gt; that will be resolved via DI or type activated.
/// &lt;/summary&gt;
/// &lt;typeparam name="TFilter"&gt;The &lt;see cref="IHubFilter"/&gt; type that will be added to the options.&lt;/typeparam&gt;
/// &lt;param name="options"&gt;The options to add a filter to.&lt;/param&gt;
public static void AddFilter&lt;[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TFilter&gt;(this HubOptions options) where TFilter : IHubFilter
{
    _ = options ?? throw new ArgumentNullException(nameof(options));

    options.AddFilter(typeof(TFilter));
}

/// &lt;summary&gt;
/// Adds an &lt;see cref="IHubFilter"/&gt; type to the &lt;see cref="HubOptions"/&gt; that will be resolved via DI or type activated.
/// &lt;/summary&gt;
/// &lt;param name="options"&gt;The options to add a filter to.&lt;/param&gt;
/// &lt;param name="filterType"&gt;The &lt;see cref="IHubFilter"/&gt; type that will be added to the options.&lt;/param&gt;
public static void AddFilter(this HubOptions options, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type filterType)
{
    _ = options ?? throw new ArgumentNullException(nameof(options));
    _ = filterType ?? throw new ArgumentNullException(nameof(filterType));

    options.AddFilter(new HubFilterFactory(filterType));
}

}

/// <summary>
/// The filter abstraction for hub method invocations.
/// </summary>
public interface IHubFilter
{
/// <summary>
/// Allows handling of all Hub method invocations.
/// </summary>
/// <param name="invocationContext">The context for the method invocation that holds all the important information about the invoke.</param>
/// <param name="next">The next filter to run, and for the final one, the Hub invocation.</param>
/// <returns>Returns the result of the Hub method invoke.</returns>
ValueTask<object?> InvokeMethodAsync(HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next) => next(invocationContext);

/// &lt;summary&gt;
/// Allows handling of the &lt;see cref="Hub.OnConnectedAsync"/&gt; method.
/// &lt;/summary&gt;
/// &lt;param name="context"&gt;The context for OnConnectedAsync.&lt;/param&gt;
/// &lt;param name="next"&gt;The next filter to run, and for the final one, the Hub invocation.&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
Task OnConnectedAsync(HubLifetimeContext context, Func&lt;HubLifetimeContext, Task&gt; next) =&gt; next(context);

/// &lt;summary&gt;
/// Allows handling of the &lt;see cref="Hub.OnDisconnectedAsync(Exception)"/&gt; method.
/// &lt;/summary&gt;
/// &lt;param name="context"&gt;The context for OnDisconnectedAsync.&lt;/param&gt;
/// &lt;param name="exception"&gt;The exception, if any, for the connection closing.&lt;/param&gt;
/// &lt;param name="next"&gt;The next filter to run, and for the final one, the Hub invocation.&lt;/param&gt;
/// &lt;returns&gt;&lt;/returns&gt;
Task OnDisconnectedAsync(HubLifetimeContext context, Exception? exception, Func&lt;HubLifetimeContext, Exception?, Task&gt; next) =&gt; next(context, exception);

}

看到这里,瞬间就明白了,就这个了

不过将HubOptions设为**internal,又弄个扩展类来维护此属性也是绝了为了防止使用者乱来真是费尽了心思...直呼一流,有需要的可以学习一下~

到此也差不多结束了,最后贴一下IHubFilter的使用位置:Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher 好巧不巧就是HubDispatcher<>,据说是HubDispatcher<>中的信息太多了,不想直接公开...

private readonly Func<HubInvocationContext, ValueTask<object>> _invokeMiddleware;
private readonly Func<HubLifetimeContext, Task> _onConnectedMiddleware;
private readonly Func<HubLifetimeContext, Exception, Task> _onDisconnectedMiddleware;

public DefaultHubDispatcher(IServiceScopeFactory serviceScopeFactory, IHubContext<THub> hubContext, bool enableDetailedErrors,
ILogger<DefaultHubDispatcher<THub>> logger, List<IHubFilter> hubFilters)
{
_serviceScopeFactory = serviceScopeFactory;
_hubContext = hubContext;
_enableDetailedErrors = enableDetailedErrors;
_logger = logger;
DiscoverHubMethods();

var count = hubFilters?.Count ?? 0;
if (count != 0)
{
    _invokeMiddleware = (invocationContext) =&gt;
    {
        var arguments = invocationContext.HubMethodArguments as object[] ?? invocationContext.HubMethodArguments.ToArray();
        if (invocationContext.ObjectMethodExecutor != null)
        {
            return ExecuteMethod(invocationContext.ObjectMethodExecutor, invocationContext.Hub, arguments);
        }
        return ExecuteMethod(invocationContext.HubMethod.Name, invocationContext.Hub, arguments);
    };

    _onConnectedMiddleware = (context) =&gt; context.Hub.OnConnectedAsync();
    _onDisconnectedMiddleware = (context, exception) =&gt; context.Hub.OnDisconnectedAsync(exception);

    for (var i = count - 1; i &gt; -1; i--)
    {
        var resolvedFilter = hubFilters[i];
        var nextFilter = _invokeMiddleware;
        _invokeMiddleware = (context) =&gt; resolvedFilter.InvokeMethodAsync(context, nextFilter);

        var connectedFilter = _onConnectedMiddleware;
        _onConnectedMiddleware = (context) =&gt; resolvedFilter.OnConnectedAsync(context, connectedFilter);

        var disconnectedFilter = _onDisconnectedMiddleware;
        _onDisconnectedMiddleware = (context, exception) =&gt; resolvedFilter.OnDisconnectedAsync(context, exception, disconnectedFilter);
    }
}

}

说明:在构造函数中将filter注册到委托中,委托的调用就不看了,有兴趣的自己去翻翻吧

posted @ 2021-01-25 16:35  前行丶  阅读(1115)  评论(0编辑  收藏  举报