Внедрение зависимостей в пользовательский вывод 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();
//...
}
}