Add a generic handler for Send and Publish methods of the MediatR library in asp .net core

This time I want to answer the question starting from the end.

2.

TL;DR Polymorphic Dispatch cannot be used for the CQS

After some time of playing with the MediatR library, reading the comments under my Question and consultation with my friend, I found the Polymorphic Dispatch(PD) can be used to create a generic handler only in case of the Commands. The PD solution cannot be implemented for Queries. Based on the Documentation, the handlers are contravariant and not covariant. This means the PD works only in the case where the TResponse is a constant type. In case of the Queries, this is false and each Query handler can return a different result.

I also found this issue. I think it's interesting to know you can use the Polymorphic Dispatch only if your container supports it.

1. Behaviors is the one and only solution for CQS when using the MediatR. Based on the comment under my question from #Steve and comment from jbogard I've found the way how to use Behaviors and IRequestHandler for the strict Command pattern. The full comment:

Just to summarize the changes, there are 2 main flavors of requests: those that return a value, and those that do not. The ones that do not now implement IRequest<T> where T : Unit. This was to unify requests and handlers into one single type. The diverging types broke the pipeline for many containers, the unification means you can use pipelines for any kind of request.

It forced me to add the Unit type in all cases, so I've added some helper classes for you.

  • IRequestHandler<T> - implement this and you will return Task<Unit>.
  • AsyncRequestHandler<T> - inherit this and you will return Task.
  • RequestHandler<T> - inherit this and you will return nothing (void).

For requests that do return values:

  • IRequestHandler<T, U> - you will return Task<U>
  • RequestHandler<T, U> - you will return U

I got rid of the AsyncRequestHandler because it really wasn't doing anything after the consolidation, a redundant base class.

The example

a) The Commands management:

public class EmptyCommand : IRequest{...}

public class EmptyCommandHandler : RequestHandler<EmptyCommand>
{
    protected override void Handle(EmptyCommand request){...}
}

b) The Queries management:

// 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");
    }
}

c) The sample LogginBehavior class:

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

}

d) To register the LoggingBehavior add the command

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));

to the body of the ConfigureServices method in the Startup.cs.

e) The example of how to run sample command and query:

await _mediator.Send(new EmptyCommand());
var result = await _mediator.Send(new EmptyQuery());

MediatR supports dispatching notifications to generic handlers (polymorphic dispatch). For example:

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

This handler will be invoked for every notification that is published through Publish(). The same is true for requests (queries/commands). You should also take a look at behaviors.

If you're using MediatR with ASP.NET Core I suggest you use the MediatR.Extensions.Microsoft.DependencyInjection library which takes care of wiring all the handlers together.