На этот раз я хочу ответить на вопрос, начиная с конца.
2.
TL; DR Полиморфная диспетчеризация не может быть использована для CQS
Через некоторое время игры сБиблиотека MediatR, прочитав комментарии к моему Вопросу и проконсультировавшись с моим другом, обнаружила, что Polymorphic Dispatch (PD) можно использовать для создания универсального обработчика только в случае команд.Решение PD не может быть реализовано для запросов.Основываясь на документации 1014 *, обработчики являются контравариантными и не ковариантными.Это означает, что PD работает только в случае, когда TResponse
является константным типом.В случае запросов это ложно, и каждый обработчик запроса может возвращать свой результат.
Я также нашел эту проблему .Я думаю, что интересно знать, что вы можете использовать Polymorphic Dispatch, только если ваш контейнер поддерживает его.
1. Поведения - это единственное решение для CQS при использованииМедиатр.На основании комментария под моим вопросом от #Steve и комментария от jbogard я нашел способ использования Behaviors и IRequestHandler для строгого шаблона Command.Полный комментарий:
Просто, чтобы подвести итог изменениям, есть 2 основных варианта запросов: те, которые возвращают значение, и те, которые не делают.Те, которые сейчас не реализуют IRequest<T>
, где T : Unit
.Это должно было объединить запросы и обработчики в один тип.Расходящиеся типы сломали конвейер для многих контейнеров, унификация означает, что вы можете использовать конвейеры для любого типа запроса.
Это вынудило меня добавить тип Unit во всех случаях, поэтому я добавил несколько вспомогательных классов дляВы.
IRequestHandler<T>
- реализуйте это, и вы вернете Task<Unit>
. AsyncRequestHandler<T>
- унаследуйте это, и вы вернете Task
. RequestHandler<T>
- унаследуйте это, и вы ничего не вернете (void)
.
Для запросов, которые возвращают значения:
IRequestHandler<T, U>
- вы вернете Task<U>
RequestHandler<T, U>
- вы вернете U
Я избавился от AsyncRequestHandler, потому что он действительно ничего не делал после консолидации, избыточного базового класса.
Пример
a) Управление командами:
public class EmptyCommand : IRequest{...}
public class EmptyCommandHandler : RequestHandler<EmptyCommand>
{
protected override void Handle(EmptyCommand request){...}
}
b) Управление запросами:
// can be any other type not necessarily `string`
public class EmptyQuery : IRequest<string>{...}
public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
{
return Task.FromResult("Sample response");
}
}
в) Пример LogginBehavior
класс:
public class LoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
{
_logger = logger;
}
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
{
var requestType = typeof(TRequest).Name;
var response = await next();
if (requestType.EndsWith("Command"))
{
_logger.LogInformation($"Command Request: {request}");
}
else if (requestType.EndsWith("Query"))
{
_logger.LogInformation($"Query Request: {request}");
_logger.LogInformation($"Query Response: {response}");
}
else
{
throw new Exception("The request is not the Command or Query type");
}
return response;
}
}
г) Чтобы зарегистрировать LoggingBehavior
, добавьте команду
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
в телометод ConfigureServices
в файле Startup.cs.
e) Пример выполнения примера команды и запроса:
await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());