.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註冊到委託中,委託的調用就不看了,有興趣的自己去翻翻吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章