Внедрение зависимостей в пользовательский вывод EventFlow

Я использую EventFlow для отслеживания событий ETW. Для этого я создал службу ASP Net Core, которая действует как прослушиватель. Я настроил свой собственный вывод в файле конфигурации. И это мой класс Output и мои классы OutputFactory:

class CustomOutput : IOutput
{
    public Task SendEventsAsync(IReadOnlyCollection<EventData> events, long transmissionSequenceNumber, CancellationToken cancellationToken)
    {
        foreach(var e in events)
        {
            //...;
        }
        return Task.CompletedTask;
    }
}

class CustomOutputFactory : IPipelineItemFactory<CustomOutput>
{
    public CustomOutput CreateItem(IConfiguration configuration, IHealthReporter healthReporter)
    {
        return new CustomOutput();
    }
}

Этот CustomOutput создается только один раз при запуске (при создании конвейера EventFlow) и используется для всех событий. Основной метод заключается в следующем:

private static void Main()
{
    try
    {
        using (var diagnosticsPipeline = ServiceFabricDiagnosticPipelineFactory.CreatePipeline("MyApplication-MyService-DiagnosticsPipeline"))
        {

            ServiceRuntime.RegisterServiceAsync("Stateless1Type",
            context => new Stateless1(context)).GetAwaiter().GetResult();

            ServiceEventSource.Current.ServiceTypeRegistered(Process.GetCurrentProcess().Id, typeof(Stateless1).Name);

            Thread.Sleep(Timeout.Infinite);
        }
    }
    catch (Exception e)
    {
        ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
        throw;
    }
}

На выходные и заводские типы выходов ссылаются в файле конфигурации eventFlowConfig.json:

"extensions": [
{
  "category": "outputFactory",
  "type": "CustomOutput",
  "qualifiedTypeName": "MyProyect.Service.MyApp.SqlOutputFactory, MyProyect.Service.MyApp"
}
]

Ссылка: агрегация и сбор событий с использованием EventFlow

Таким образом, экземпляр создается в методе main моего класса Program, то есть до того, как будут вызваны мои методы конфигурации запуска.

Как я могу получить доступ из моего класса Output к моим службам контейнера зависимостей, если контейнер еще не существует, когда он создается?

На данный момент я создал статическое свойство типа IServiceCollection и установил его из моего метода конфигурации запуска (с использованием установки сеттера). Мне не нравится это решение, потому что я не должен использовать статический доступ к сервисам, но я не знаю другого решения. Это действительная практика?

class CustomOutput : IOutput
{
    public static IServiceCollection Services { get; set; }

    public Task SendEventsAsync(IReadOnlyCollection<EventData> events, long transmissionSequenceNumber, CancellationToken cancellationToken)
    {
        var sp = Services.BuildServiceProvider();
        var loggerFactory = sp.GetService<ILoggerFactory>();
        logger = loggerfactory.CreateLogger<CustomOutput>();
        var repository = serviceProvider.GetService<IMyRepository>();
        foreach (var e in events)
        {
            logger.LogDebug("event...");
            repository.SaveEvent(e);
            //...;
        }
        return Task.CompletedTask;
    }
}

public class Startup
{
    // Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {

        //..
        CustomOutput.Services = services;
        //..
    }
}

1 ответ

Решение

При использовании принципа явной зависимости в отличие от внедряемого в настоящее время шаблона локатора служб, это был бы лучший выбор. Ограничения расширяемости целевой платформы затрудняют это.

Оставляет только статические средства доступа в качестве точки расширения для возможного решения.

Так как CustomOutput будет создаваться только один раз, тогда следующий шаблон должен работать в этом дизайне

public class CustomOutput : IOutput {
    private static Lazy<CustomOutput> instance = 
        new Lazy<CustomOutput>(() => return new CustomOutput());
    private Lazy<ILogger> logger;
    private Lazy<IMyRepository> repository;

    private CustomOutput() { }

    public static CustomOutput Instance {
        get {
            return instance.Value;
        }
    }

    public void Configure(Lazy<ILogger> logger, Lazy<IMyRepository> repository) {
        this.logger = logger;
        this.repository = repository
    }

    public Task SendEventsAsync(IReadOnlyCollection<EventData> events, long transmissionSequenceNumber, CancellationToken cancellationToken) {
        //TODO: Add null check and fail if not already configured.

        foreach (var e in events) {
            logger.Value.LogDebug("event...");
            repository.Value.SaveEvent(e);
            //...;
        }
        return Task.CompletedTask;
    }
}

public class CustomOutputFactory : IPipelineItemFactory<CustomOutput> {
    public CustomOutput CreateItem(IConfiguration configuration, IHealthReporter healthReporter) {
        return CustomOutput.Instance;
    }
}

В вышеупомянутом подходе создание и внедрение зависимостей может быть отложено до Startup, Следующее расширение облегчает это.

public static class CustomOutputServiceCollectionExtensions {

    public IServiceCollection ConfigureCustomOutput(this IServiceCollection services) {
        services.AddTransient<IMyRepository, MyRepository>();

        var logger = new Lazy<ILogger>(() => {
            var sp = services.BuildServiceProvider();
            return sp.GetService<ILogger<CustomOutput>>();
        });

        var repository = new Lazy<IMyRepository>(() => {
            var sp = services.BuildServiceProvider();
            return sp.GetService<IMyRepository>();
        });

        CustomOutput.Instance.Configure(logger, repository);

        return services;
    }

}

Который затем будет вызван из Startup;

public class Startup {

    //...

    // Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services) {

        //...

        services.ConfigureCustomOutput();

        //...
    }
}
Другие вопросы по тегам