Dependency Injection in EventFlow custom output

724 views Asked by At

I'm using EventFlow to trace ETW events. For this I've created an ASP Net Core service that acts as a listener. I've configured my own custom output in my configuration file. And these are my Output and my OutputFactory classes:

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();
    }
}

This CustomOutput is instantiated only one time at start (when EventFlow pipeline is created) and it's used for all the events. The main method is this:

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;
    }
}

The output and factory output types are referenced in the configuration file eventFlowConfig.json:

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

Reference: Event aggregation and collection using EventFlow

Thus, the instance is created in the main method of my Program class, that is, before my Startup configuration methods are invoked.

How could I access from my Output class to my dependency container services if the container still does not exist when it's instantiated?

At the moment I've created a static property of type IServiceCollection and I set it from my Startup config method (using setter injection). I don't like this solution because I shouldn't use static access to services but I don't know another solution. It's this a valid practice?

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

There are 1 answers

5
Nkosi On BEST ANSWER

While using the Explicit Dependency Principle as opposed to the Service Locator Pattern currently being implemented would be the better choice. Limitations of the target framework's extensibility make this difficult.

The leaves only static accessors as the extensibility point for a possible solution.

Since the CustomOutput is only going to be created once, then following a singleton patter should work in this design

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;
    }
}

In the above approach, the creation and injection of the dependencies can be deferred to the Startup. The following extension facilitates this.

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;
    }

}

Which would then be called from Startup;

public class Startup {

    //...

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

        //...

        services.ConfigureCustomOutput();

        //...
    }
}