Как реализовать ILogger для отправки сообщений в SignalR Hub? - PullRequest
3 голосов
/ 14 января 2020

Я хочу создать LogView, который показывает последние сообщения журнала. Итак, я построил очень простую настройку, но мне не удается выполнить Dependency Injection.

Вот моя попытка реализации. Я пропустил несущественные части.

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, и я не уверен, как решить эту проблему

1 Ответ

4 голосов
/ 14 января 2020

Проблема в том, что LoggerProvider создается до регистрации SignalR концентраторов. Поэтому, когда поставщик регистратора создан, IServiceProvider не был инициализирован, чтобы знать о каких-либо IHubContext<T> объектах.

Единственный способ, который я могу найти, - это предоставить IServiceProvider вашему * 1008. * и пусть он получает экземпляр 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 вызове - но это даст нам правильный интерфейс для использования.

...