4

我想构建一个显示最新日志消息的 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并且我不知道如何解决这个问题

4

1 回答 1

4

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

我能找到解决它的唯一方法是提供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- 但它会给我们提供正确的接口来使用。

于 2020-01-14T02:36:25.633 回答