сразу выделю вопрос, ниже будет подробное описание для чего мне это надо.
В Autofac
зарегистрированы зависимости следующего вида
ICommandHandlerAsync<ChangeEmailCi> -> UpdateEmailHandler
ICommandHandlerAsync<ChangeEmailCi> -> SendNotificationUpdateEmailHandler
ICommandHandlerAsync<UpdateUserCi> -> UpdateUserHandler
ICommandHandlerAsync<UpdateUserCi> -> SendNotificationUpdateUserHandler
мне нужно без указания конкретного типа Tcommand
разрезолвить список всех зависимостей. Т.е. хочу получить все 4 зависимости.
var resolvedHandlers = ctx.Resolve(typeof(IList<ICommandHandlerAsync<>>)); //ТУТ Exception.
Используя IServiceCollection
из встроенного IoC
можно получить массив ServiceDescriptor[]
и на базе его получить все зависимости типа ICommandHandlerAsync<>
var handlerTypes = new Dictionary<Type, IEnumerable<object>>();
ServiceDescriptor[] descriptors = services
.Where(t => t.ServiceType.IsGenericType && t.ServiceType.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>))
.ToArray();
ServiceProvider? provider = services.BuildServiceProvider();
foreach (var serviceDescriptor in descriptors)
{
handlerTypes[serviceDescriptor.ServiceType] = provider.GetServices(serviceDescriptor.ServiceType);
}
Использую на проекте Диспетчер команд, который по типу переданной команды ICommandHandlerAsync<TCommand>
находит список обработчиков в словаре.
public class CommandDispatcher
{
private readonly Dictionary<Type, IEnumerable<object>> _handlers;
private readonly ILogger<CommandDispatcher>? _logger;
public CommandDispatcher(
Dictionary<Type, IEnumerable<object>> handlers,
ILogger<CommandDispatcher>? logger = null
)
{
_handlers = handlers;
_logger = logger;
}
public async Task<Result> DispatchAsync<TCommand>(TCommand command) where TCommand : class
{
_logger?.LogInformation($"Receiving command {command.GetType()}");
// Извлечем тип переданной команды
Type handlerType = typeof(ICommandHandlerAsync<>).MakeGenericType(command.GetType());
_handlers.TryGetValue(handlerType, out IEnumerable<object>? concreteHandlers);
if (concreteHandlers is null) throw new ArgumentException($"No handler registered for {command.GetType()}");
var resultList= new List<Result>();
foreach (object concreteHandler in concreteHandlers)
{
if (!(concreteHandler is ICommandHandlerAsync<TCommand> handler)) throw new ArgumentException($"NOT VALID handler registered for {command.GetType()}");
_logger?.LogInformation($"Invoking handler {handler.GetType()}");
var res= await handler.HandleAsync(command);
resultList.Add(res);
}
var combinedResult= Result.Combine(resultList);
return combinedResult;
}
}
Команда и обработчик команды
public class ChangeEmailCi
{
public ChangeEmailCi(string oldEmail, string newEmail)
{
if (string.IsNullOrEmpty(oldEmail) || string.IsNullOrEmpty(newEmail))
throw new ArgumentException();
OldEmail = oldEmail;
NewEmail = newEmail;
}
public string OldEmail { get; }
public string NewEmail { get; }
}
public class UpdateEmailHandler : ICommandHandlerAsync<ChangeEmailCi>
{
private readonly IUserRepository _repository;
public UpdateEmailHandler(IUserRepository repository)
{
_repository = repository;
}
public async Task<Result> HandleAsync(ChangeEmailCi command)
{
//Update Email in _repository
}
}
Я использовал встроенный Ioc
контейнер и вот так регистрировал CommandDispatcher с словарем всех команд и их обработчиков.
public static IServiceCollection AddCommandDispatcher(this IServiceCollection services)
{
var loggerFactory = services.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
ILogger logger = loggerFactory.CreateLogger(typeof(ServiceInjector));
logger.LogInformation("Initiating discovery process for command handlers.");
Type[] handlers = Assembly.GetExecutingAssembly().GetTypes()
// Find all types where
.Where(t => t.IsClass && // the type is a class
t.GetInterfaces() // and the type implements the generic type definition ICommandHandlerAsync
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>)))
.ToArray();
logger.LogInformation($"Found {handlers.Length} command handlers.");
// Register the interface with the concrete handler.
// Note: the current limitation is, every handler must be in a separate class.
// This won't work with a single class implementing the ICommandHandlerAsync<> multiple times.
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("--- Registered handlers ---");
foreach (Type concreteHandler in handlers) {
Type? handlerInterface = concreteHandler
.GetInterfaces()
.First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>));
stringBuilder.AppendLine($"{concreteHandler.Name} -> {handlerInterface}");
services.AddScoped(handlerInterface, concreteHandler);
}
logger.LogInformation(stringBuilder.ToString());
var handlerTypes = new Dictionary<Type, IEnumerable<object>>();
ServiceDescriptor[] descriptors = services
.Where(t => t.ServiceType.IsGenericType && t.ServiceType.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>))
.ToArray();
ServiceProvider? provider = services.BuildServiceProvider();
foreach (var serviceDescriptor in descriptors)
{
handlerTypes[serviceDescriptor.ServiceType] = provider.GetServices(serviceDescriptor.ServiceType);
}
// this factory method is called every time a CommandDispatcher is required
services.AddScoped(provider => new CommandDispatcher(handlerTypes, provider.GetRequiredService<ILogger<CommandDispatcher>>()));
return services;
}
Перешел на Autofac и нужно повторить регистрацию AddCommandDispatcher
.
public class CommandDispatcherAutofacModule : Module
{
private readonly ILogger _logger;
public CommandDispatcherAutofacModule(ILogger logger)
{
_logger = logger;
}
protected override void Load(ContainerBuilder builder)
{
_logger?.LogInformation("Initiating discovery process for command handlers.");
Type[] handlers = Assembly.GetExecutingAssembly().GetTypes()
// Find all types where
.Where(t => t.IsClass && // the type is a class
t.GetInterfaces() // and the type implements the generic type definition ICommandHandlerAsync
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>)))
.ToArray();
_logger?.LogInformation($"Found {handlers.Length} command handlers.");
//РЕГИСТРАЦИЯ ВСЕХ ОБРАБОТЧИКОВ КОММАНД ПОД ИНТЕРФЕЙСАМИ ВИДА ICommandHandlerAsync<>
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("--- Registered handlers ---");
foreach (Type concreteHandler in handlers)
{
Type? handlerInterface = concreteHandler.GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>));
stringBuilder.AppendLine($"{concreteHandler.Name} -> {handlerInterface}");
builder.RegisterType(concreteHandler).As(handlerInterface).InstancePerLifetimeScope();
}
_logger?.LogInformation(stringBuilder.ToString());
//РЕГИСТРАЦИЯ CommandDispatcher.
builder.RegisterType<CommandDispatcher>()
.WithParameters(new List<ResolvedParameter>
{
new ResolvedParameter(
(pi, ctx) => (pi.ParameterType == typeof(Dictionary<Type, IEnumerable<object>>) && (pi.Name == "handlers")),
(pi, ctx) =>
{
var handlersDict = new Dictionary<Type, IEnumerable<object>>();
//ХОЧУ РЕЗОЛВИТЬ ВЕСЬ СПИСОК ЗАВИСИМОСТЕЙ ТИПА ICommandHandlerAsync<>
var resolvedHandlers = ctx.Resolve(typeof(IList<ICommandHandlerAsync<>>));
//Но так не работает, нужно указать точный тип, например typeof(IList<ICommandHandlerAsync<AcceptApplicationCi>>)
//После того как я получу все зависимости типа ICommandHandlerAsync<>, я сгруппирую их по типу и мапну в словарь.
//Таким образом будет готова фабрика по резолву CommandDispatcher.
return handlersDict;
})
}).InstancePerLifetimeScope();
}
}
P.S. Реализация если в диспетчере под одной командой один обработчик.
builder.RegisterType<CommandDispatcher>()
.WithParameters(new List<ResolvedParameter>
{
new ResolvedParameter(
(pi, ctx) => (pi.ParameterType == typeof(Dictionary<Type, IEnumerable<object>>) && (pi.Name == "handlers")),
(pi, ctx) =>
{
var allDescriptors = new List<object>();
foreach (var concreteHandler in handlers)
{
var handlerInterface = concreteHandler.GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>));
object resolvedCh = ctx.Resolve(handlerInterface); //Тут резолв первой зарегистрированной зависисоти, а нужен список зависимостей
allDescriptors.Add(resolvedCh);
//Если резолвить в ручную список то все РАБОТАЕТ.
// dynamic descriptors1 = ctx.Resolve(typeof(IList<ICommandHandlerAsync<ChangeEmailCi>>));
// dynamic descriptors2 = ctx.Resolve(typeof(IList<ICommandHandlerAsync<UpdateUserCi>>));
//allDescriptors.AddRange(descriptors1);
//allDescriptors.AddRange(descriptors2);
}
var handlersDict= allDescriptors
.GroupBy(
k => k.GetType().GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>)),
o => o
)
.ToDictionary(gr => gr.Key, gr => gr.AsEnumerable());
return handlersDict;
})
}).InstancePerLifetimeScope();
Andrei Khotko помог найти недостающий пазл.
Используя MakeGenericType
можно задавать составные типы дженериков динамически.
typeof(IList<>).MakeGenericType(handlerInterface)
Как регистрировать Диспетчер комманд в Autofac
public class CommandDispatcherAutofacModule : Module
{
private readonly ILogger _logger;
public CommandDispatcherAutofacModule(ILogger logger)
{
_logger = logger;
}
protected override void Load(ContainerBuilder builder)
{
_logger?.LogInformation("Initiating discovery process for command handlers.");
Type[] handlers = Assembly.GetExecutingAssembly().GetTypes()
// Find all types where
.Where(t => t.IsClass && // the type is a class
t.GetInterfaces() // and the type implements the generic type definition ICommandHandlerAsync
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>)))
.ToArray();
_logger?.LogInformation($"Found {handlers.Length} command handlers.");
//РЕГИСТРАЦИЯ ВСЕХ ОБРАБОТЧИКОВ КОММАНД ПОД ИНТЕРФЕЙСАМИ ВИДА ICommandHandlerAsync<>
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("--- Registered handlers ---");
foreach (Type concreteHandler in handlers)
{
Type? handlerInterface = concreteHandler.GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>));
stringBuilder.AppendLine($"{concreteHandler.Name} -> {handlerInterface}");
builder.RegisterType(concreteHandler).As(handlerInterface).InstancePerLifetimeScope();
}
_logger?.LogInformation(stringBuilder.ToString());
//Выделим уникальные типы интересов обработчиков и преобразуем их в тип коллекции для резолва, чтобы при резолве извлечь ИМЕНЕНО список зависимостей.
var interfaces4Resolve = handlers
.Select(h => h.GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>)))
.Distinct()
.Select(handlerInterface => typeof(IList<>).MakeGenericType(handlerInterface))
.ToList();
builder.RegisterType<CommandDispatcher>()
.WithParameters(new List<ResolvedParameter>
{
new ResolvedParameter(
(pi, ctx) => (pi.ParameterType == typeof(Dictionary<Type, IEnumerable<object>>) && (pi.Name == "handlers")),
(pi, ctx) =>
{
//Резолвим интерфейсы обработчиков команд и получаем сами обработчики команд.
var realCommandHandlers = new List<object>();
foreach (dynamic commandHandlers in interfaces4Resolve.Select(hi => ctx.Resolve(hi))) //заказали резолв IList<> поэтому ждем список обработчиков
{
realCommandHandlers.AddRange(commandHandlers);
}
//Список обработчиков команд преобразуем в словарь.
var handlersDict= realCommandHandlers
.GroupBy(
k => k.GetType().GetInterfaces().First(i => i.GetGenericTypeDefinition() == typeof(ICommandHandlerAsync<>)),
o => o
)
.ToDictionary(gr => gr.Key, gr => gr.AsEnumerable());
return handlersDict;
})
}).InstancePerLifetimeScope();
}
}
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Как Entity Framework отслеживает изменение объектов? Еще когда через методы идет вызов понятно, а как он отслеживает объект который мы получили,...
Вообщем на практике работаю в c# windows формах (в vs 2019)Хотел подключить базу данных
Нужно написать эффективную программу бинарного поиска поиска правой границы в упорядоченном массивеПрограмма должна удовлетворять условиям:...