MediatR icon indicating copy to clipboard operation
MediatR copied to clipboard

Using the new Exception API with a generic registration

Open andygithub opened this issue 5 years ago • 4 comments

Hi,

I was looking at the examples and tests of the new error handling: ` public class PingPongExceptionHandlerForType : IRequestExceptionHandler<Ping, Pong, PingException> { public Task Handle(Ping request, PingException exception, RequestExceptionHandlerState<Pong> state, CancellationToken cancellationToken) { state.SetHandled(new Pong() { Message = exception.Message + " Handled by Type" });

            return Task.CompletedTask;
        }
    }`

And then the registration is looking like this:

cfg.For<IRequestExceptionHandler<Ping, Pong, Exception>>().Use<PingPongExceptionHandler>();

I'm interested in having a more generic error handler that would be fired for any commands and not just a single command. Is it possible to build a handler with a more generic signature like this: public class CommonExceptionHandler<TRequest, TResponse> : IRequestExceptionHandler<TRequest,TResponse> So that registration would be similar to any other pipeline?

services.AddScoped(typeof(IRequestExceptionHandler<,>), typeof(CommonExceptionHandler<,>)); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>)); services.AddScoped(typeof(IPipelineBehavior<,>), typeof(CommandValidationBehavior<,>));

andygithub avatar Jan 22 '20 18:01 andygithub

@andygithub

I was able to register it using the AspNetCore.DI by doing the following

Program.cs

public static class Program
{
    public static Task Main(string[] args)
    {
        var writer = new WrappingWriter(Console.Out);
        var mediator = BuildMediator(writer);
        mediator.Send(new PingResource());
        return Runner.Run(mediator, writer, "ASP.NET Core DI");
    }

    private static IMediator BuildMediator(WrappingWriter writer)
    {
        var services = new ServiceCollection();

        services.AddSingleton<TextWriter>(writer);

        services.AddMediatR(typeof(Ping));

        services.AddScoped(typeof(IPipelineBehavior<,>), typeof(GenericPipelineBehavior<,>));
        services.AddScoped(typeof(IRequestPreProcessor<>), typeof(GenericRequestPreProcessor<>));
        services.AddScoped(typeof(IRequestPostProcessor<,>), typeof(GenericRequestPostProcessor<,>));
        services.AddScoped(typeof(IRequestExceptionHandler<,,>), typeof(ConnectionExceptionHandler<,,>));

        var provider = services.BuildServiceProvider();

        return provider.GetRequiredService<IMediator>();
    }
}

ExceptionHandler.cs

public class ConnectionExceptionHandler<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException>
        where TException : Exception 
{
    private readonly TextWriter _writer;

    public ConnectionExceptionHandler(TextWriter writer) => _writer = writer;

    public async Task Handle(TRequest request, TException exception, RequestExceptionHandlerState<TResponse> state, CancellationToken cancellationToken)
    {
        await _writer.WriteLineAsync($"---- Exception Handler: 'ConnectionExceptionHandler'").ConfigureAwait(false);
    }
}

Hope this helps.

kaffeebrauer avatar Jan 26 '20 22:01 kaffeebrauer

Hi,

Thanks for the information. When I take that class and registration and throw an exception, I see that log entry four times. Is this the expected behavior for the registration you showed?

andygithub avatar Jan 27 '20 12:01 andygithub

Think you can control the behavior with: state.SetHandled(default);

this will short circuit the pipeline so only one message will get written. Honestly not sure if this is designed behavior, but for my scenario it seems to work fine.

MirzaMerdovic avatar Mar 01 '21 20:03 MirzaMerdovic

The info here is very helpful, I couldn't make sense of the docs or examples. I'm glad for the examples in this repo, but they're huge bites of information (and tangled together). I was able to put together an example focused on a toy in-memory message pipeline, with request logging and a root level exception handler. Not for use in prod 😄 One specific way my example sucks is there is no concern for scoped tasks! Ideally each AsyncRequestHandler would be instantiated in its own scope.

EDIT: The dotnet DI mediatr repo has a good example!

djeikyb avatar May 24 '21 23:05 djeikyb