【问题标题】:How to implement an ILogger to send messages to a SignalR Hub?如何实现 ILogger 以将消息发送到 SignalR Hub?
【发布时间】:2020-01-13 23:15:44
【问题描述】:

我想构建一个显示最新日志消息的 LogView。所以我建立了一个非常简单的设置,但我在依赖注入中失败了。

这是我的实现尝试。我跳过了不重要的部分。

public class SignalRLogger : ILogger
{
    private readonly IHubContext<LogHub> _hub;

    public SignalRLogger(IHubContext<LogHub> hub)
    {
        _hub = hub;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var msg = $"[{logLevel}] {formatter(state, exception)}";

        _hub.Clients.All.SendAsync("ReceiveMessage", msg);
    }
}

public class SignalrRLoggerProvider : ILoggerProvider
{
    private SignalRLogger _logger;

    public void Dispose()
    {
        _logger = null;
    }

    public ILogger CreateLogger(string categoryName)
    {
        if (_logger == null)
        {
            _logger = new SignalRLogger(????);
        }

        return _logger;
    }
}

我的问题基本上是我无法注入IHubContext,我不知道如何解决这个问题

【问题讨论】:

    标签: c# asp.net-core signalr ilogger


    【解决方案1】:

    问题是LoggerProvider 是在SignalR 集线器注册之前创建的。因此,当创建记录器提供程序时,IServiceProvider 尚未初始化以了解任何 IHubContext&lt;T&gt; 对象。

    我能找到解决它的唯一方法是将IServiceProvider 提供给您的ILogger,并让它在需要时获取IHubContext 的实例。

    您的记录器类需要在其构造函数中接受IServiceProvider,然后使用该对象按需检索IHubContext

    public class SignalRLogger : ILogger
    {
        private readonly IServiceProvider _sp;
    
        public SignalRLogger(IServiceProvider sp)
        {
            _sp = sp;
        }
    
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            var hub = _sp.GetService<IHubContext<LogHub>>();
    
            if (hub != null)
            {
                var msg = $"[{logLevel}] {formatter(state, exception)}";
    
                hub.Clients.All.SendAsync("ReceiveMessage", msg);
            }
        }
    }
    

    我们需要检查IHubContext 是否已从服务提供商处检索到,因为可以在注册中心之前调用记录器。这是使用 SignalR 作为记录器的缺点之一,因为您会在访问 HubContext 之前错过一些早期消息(但这可能是您可以接受的)。

    注意:您可以改进它以存储集线器,而不是在每次日志调用时获取它 - 但我会留给您实现 :-)

    现在您需要更改您的提供程序以在创建记录器时提供该参数:

    public class SignalrRLoggerProvider : ILoggerProvider
    {
        private SignalRLogger _logger;
        private readonly IServiceProvider _sp;
    
        public SignalrRLoggerProvider(IServiceProvider sp)
        {
            _sp = sp;
        }
    
        public ILogger CreateLogger(string categoryName)
        {
            if (_logger == null)
            {
                _logger = new SignalRLogger(_sp);
            }
    
            return _logger;
        }
    }
    

    最后,您需要将IServiceProvider 提供给记录器提供者:

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            })
            .ConfigureLogging(builder =>
            {
                var sp = builder.Services.BuildServiceProvider();
    
                builder.AddProvider(new SignalrRLoggerProvider(sp));
            });
    

    我们需要调用BuildServiceProvider 方法来创建我们需要的接口,因为它在ConfigureLogging 调用中不可用 - 但它会给我们提供正确的接口来使用。

    【讨论】:

    • 好的,这很好用。创建过程和一切正常。但是在发送消息时,它们不会在客户端收到。我认为问题在于集线器是瞬态的——这可能吗?无论如何,这个问题与问题无关。我应该接受答案吗?我的意思是它(有点)解决了我的问题
    • 当我将 IHubContext 注入后台任务时,客户端正在接收消息。 ServiceProvider创建ctx时似乎不会收到它
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多