jbogard / MediatR

Simple, unambitious mediator implementation in .NET
Apache License 2.0
11k stars 1.16k forks source link

Control order of IRequestPreProcessor and IRequestPostProcessor #885

Closed DanTm99 closed 1 year ago

DanTm99 commented 1 year ago

When registering IRequestPreProcessors and IRequestPostProcessors implicitly like the example in the readme:

services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly)
    .AddOpenBehavior(typeof(MyBehavior<,>));
});

is it possible to specify the order in which they're run when there are multiple preprocessors, or multiple post processors? There doesn't seem to be anything in the wiki that allows for this.

Currently if I register them explicitly:

services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Startup).Assembly);
    .AddBehavior(typeof(IRequestPreProcessor<>), typeof(MyFirstPreProcessor<>))
    .AddBehavior(typeof(IRequestPreProcessor<>), typeof(MySecondPreProcessor<>))
    .AddOpenBehavior(typeof(MyBehavior<,>))
    .AddBehavior(typeof(IRequestPostProcessor<,>), typeof(MyFirstPostProcessor<,>))
    .AddBehavior(typeof(IRequestPostProcessor<,>), typeof(MySecondPostProcessor<,>));
})

the preprocessors and the postprocessors run twice, due to them already having been registered implicitly.

Tommo56700 commented 1 year ago

@jbogard Is there a good example of how to control the execution order of pre/post processor behaviors when you have multiple? Perhaps one could be added to the wiki.

jbogard commented 1 year ago

No, I don't have a good example of that. Perhaps that needs to be exposed explicitly in the API, some other folks have also had problems of the order of auto-registered items.

maxime-poulain commented 1 year ago

One possiblity could be cfg.AddPreProcessor and have cfg.AddPostPreccessor, heh, this will cause breaking changes.

Another way around could be to declare the order itself by introducing a public int Order {get; } property.

This looks a bit weird to me, but it should work without introducing breaking changes.

This is from PreProc source code, see comments:

namespace MediatR.Pipeline;

public class RequestPreProcessorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : notnull
{
    private readonly IEnumerable<IRequestPreProcessor<TRequest>> _preProcessors;

    public RequestPreProcessorBehavior(IEnumerable<IRequestPreProcessor<TRequest>> preProcessors) 
        => _preProcessors = OrderPreprocessors(preProcessors); // Orders the prepocessors 
                                                               // only if any of those declares a different value than zero
                                                               // to keep compatiblity with existing implementations;

    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        foreach (var processor in _preProcessors)
        {
            await processor.Process(request, cancellationToken).ConfigureAwait(false);
        }

        return await next().ConfigureAwait(false);
    }

    private static IEnumerable<IRequestPreProcessor<TRequest>> OrderPreprocessors(
        IEnumerable<IRequestPreProcessor<TRequest>> preProcessors)
    {
        if (preProcessors.All(preProcessor => proProcessor.Order == 0)
        {    
            return preProcessors; // backward compatiblity
        }

        return preProcessors.OrderBy(p => p.Order);
    }
}

Potential issues:

Well I don't use pre and post processors anyway :D.

jbogard commented 1 year ago

I think it isn't just the auto-registration, it's that the code will add the interface if it already exists: https://github.com/jbogard/MediatR/blob/master/src/MediatR/Registration/ServiceRegistrar.cs#L21

I can't remember why the code does this though.