Добавьте универсальный обработчик для методов отправки и публикации библиотеки MediatR в ядре asp .net - PullRequest
0 голосов
/ 22 декабря 2018

Я использую шаблон CQS в своем основном проекте asp.net.Давайте начнем с примера, чтобы лучше объяснить, чего я хочу достичь.Я создал команду:

public class EmptyCommand : INotification{}

Обработчик команды:

public class EmptyCommandHandler : INotificationHandler<EmptyCommand>
{
    public Task Handle(EmptyCommand notification, CancellationToken cancellationToken)
    {
        return Task.FromResult(string.Empty);
    }
}

Запрос:

public class EmptyQuery : IRequest<string>{}

Обработчик запроса:

public class EmptyQueryHandler : IRequestHandler<EmptyQuery, string>
{
    public Task<string> Handle(EmptyQuery notification, CancellationToken cancellationToken)
    {
        return Task.FromResult(string.Empty);
    }
}

и это простой пример того, как запустить команду и запрос и вызвать метод Handle из EmptyCommandHandler и EmptyQueryHandler:

readonly IMediator _mediator;

public HomeController(IMediator mediator)
{
    _mediator = mediator;
}

public async Task<IActionResult> Index()
{
    await _mediator.Publish(new EmptyCommand());
    var queryResult = await _mediator.Send(new EmptyQuery());   
    return View();  
}

Пожалуйста, имейте в виду, что запрос может возвращать другие типы, не обязательно string.Я хотел бы создать некоторый класс моста, например MediatorBoostrapper, который позволит мне запускать некоторую бизнес-логику (например, команда / запрос журнала через Logger) каждый раз, когда вызывается метод Publish, а затем вызывать метод public Task Handle(EmptyCommand notification,... изобработчик команд.Решение должно быть общим, поэтому этот метод будет вызываться каждый раз, когда я запускаю метод Publish.Я также хочу иметь возможность сделать то же самое для метода Send.

Я думал о создании public class MediatorBoostrapper : IMediator, но не уверен, что должно быть правильной реализацией класса и если мойидея хорошаяЕсть идеи?Приветствия

Редактировать

  1. Я хочу привести пример использования Поведения для создания общего способазапускать какой-либо внешний метод из универсального обработчика каждый раз, когда я запускаю метод Send для запросов.Я хочу иметь подобный пример для метода Publish, который я использую для отправки команд.

  2. Я хочу иметь пример использования Полиморфная диспетчеризация для создания GenericCommandHandler и GenericQueryHandler

Я создал пример проекта на GitHub, который можно найти здесь Вы можете попробовать расширить этот проект с помощьюваше решение.

Ответы [ 2 ]

0 голосов
/ 04 января 2019

На этот раз я хочу ответить на вопрос, начиная с конца.

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());
0 голосов
/ 22 декабря 2018

MediatR поддерживает отправку уведомлений универсальным обработчикам ( полиморфная отправка ).Например:

public class GenericHandler<TNotification> : INotificationHandler<TNotification> 
    where TNotification : INotification
{
    public Task Handle(TNotification notification, CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

Этот обработчик будет вызываться для каждого уведомления, опубликованного через Publish().То же самое верно для запросов (запросов / команд).Вам также следует взглянуть на поведения .

Если вы используете MediatR с ASP.NET Core, я предлагаю вам использовать MediatR.Extensions.Microsoft.DependencyInjection библиотека, которая заботится о соединении всех обработчиков.

...