khellang / Scrutor

Assembly scanning and decoration extensions for Microsoft.Extensions.DependencyInjection
MIT License
3.63k stars 239 forks source link

Decorating command handlers #179

Open fmontazeri opened 2 years ago

fmontazeri commented 2 years ago

Hi dear, I want to decorate command handlers with decorator Pattern in an ASP.NET Core app. So I registered command handers first and then register decorators. But it doesn't work. I am using built-in IoC in ASP.NET Core and Scrutor. You can see them below.

  public interface IMediatRCommandHandler<in TCommand, TResult> : IRequestHandler<TCommand, TResult>
     where TCommand : IMediatRCommand<TResult>
   {
   }

 public class TestCommandHandler : IMediatRCommandHandler<TestCommand, TestResult>
     {
         //implementation

     }
 public class TransactionalCommandHandlerDecorator<TCommand, TResult> : IMediatRCommandHandler<TCommand, TResult> , IHandlerDecorator
         where TCommand : IMediatRCommand<TResult>
     {
              private readonly IMediatRCommandHandler<TCommand, TResult> _decoratee;

             // implementation
     }

Registration of command handlers and decorators

 services.Scan(s => s.FromAssemblies(typeof(TestDecoratorCommand).Assembly)
                 .AddClasses(c =>c.Where(i => !typeof(IHandlerDecorator).IsAssignableFrom(i)).AssignableToAny(typeof(IMediatRCommandHandler<>), typeof(IMediatRCommandHandler<,>)))
                 .AsImplementedInterfaces()
                 .WithScopedLifetime());

  services.Decorate(typeof(IMediatRCommandHandler<,>), typeof(TransactionalCommandHandlerDecorator<,>)); // This line doesn't work

Could you tell me how can I do that , please? Thanks in advance

jhenriquez commented 1 year ago

This is an old post but reading it made me realize I was not filtering out my decorators at registration time, which I think is a good idea. Bellow, I outlined what worked for me.

public interface ICommandHandler<in TCommand, TResponse> where TCommand : ICommand where TResponse: class
{
    Task<Result<TResponse>> Handle(TCommand command);
}
// Decorators

public class CommandExceptionHandlingBehavior<TCommand, TResult> : ICommandHandler<TCommand, TResult> where TCommand : ICommand where TResult : class
{
    private ICommandHandler<TCommand, TResult> NextCommandHandler { get; }

    public CommandExceptionHandlingBehavior(ICommandHandler<TCommand, TResult> nextCommandHandler)
    {
        NextCommandHandler = nextCommandHandler;
    }

    public async Task<Result<TResult>> Handle(TCommand command)
    {
        try
        {
            return await NextCommandHandler.Handle(command);
        }
        catch (QuotaExceededException ex)
        {
            return Result<TResult>.Failure(ex.AsError());
        }
        catch (Exception ex)
        {
            return Result<TResult>.Failure(new Error
            {
                Message = ex.Message,
                Reference = NextCommandHandler.GetType().ToString(),
                Type = Error.ErrorTypes.UnknownError
            });
        }
    }
}

public class CommandValidationBehavior<TCommand, TResult> : ICommandHandler<TCommand, TResult> where TCommand : ICommand where TResult : class
{
    private ICommandHandler<TCommand, TResult> NextCommandHandler { get; }
    private IValidator<TCommand> Validator { get; }

    public CommandValidationBehavior(ICommandHandler<TCommand, TResult> nextCommandHandler, IValidator<TCommand> validator)
    {
        NextCommandHandler = nextCommandHandler;
        Validator = validator;
    }

    public async Task<Result<TResult>> Handle(TCommand command)
    {
        var errors = await Validator.ValidateAsync(command);

        if (errors.Any())
        {
            return Result<TResult>.Failure(errors);
        }

        return await NextCommandHandler.Handle(command);
    }
}
// IoC Configurations

services.Scan(scan => scan.FromCallingAssembly()
           // First gather all the possible implementations of my commands and then filter out the decorators
            .AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<,>)).Where(c => !c.IsAssignableTo(typeof(ICommandBehavior))))
                .AsImplementedInterfaces()
                .WithTransientLifetime()
);

services.Decorate(typeof(ICommandHandler<,>), typeof(CommandValidationBehavior<,>));

services.Decorate(typeof(ICommandHandler<,>), typeof(CommandExceptionHandlingBehavior<,>));