jbogard / MediatR

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

Skipping Pipeline when constraint applied for IRequest<TResponse> #975

Closed irpg7 closed 6 months ago

irpg7 commented 8 months ago

Hello,

image

when i apply this constraint on my validation behavior. Its just skips the pipeline process, when i don't need a response from command. but i actually need to apply some validation rules. also when i remove the TResponse, it works only for IRequest ones.

image

doing these kinda commands, always skips the validation part, which is valid because it doesn't have a response parameter. But i think i should be able to validate even if i don't have response on my "out" part. i believe that creates some confusion. can you sort it out ? Thanks

github-actions[bot] commented 6 months ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 14 days.

irpg7 commented 6 months ago

Up @jbogard

Shiney commented 6 months ago

Hello, you should be able to just remove the type constraint completely. e.g. see WithEitherPipelineBehaviour below, if it matters for some reason you could also replace the type constraint with where TRequest : IBaseRequest

using MediatR;
using MediatR.Pipeline;
using Microsoft.Extensions.DependencyInjection;
using mytest;

// Create a new service collection and add mediatr
var services = new ServiceCollection();
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(NoReturnPipelineBehaviour<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(WithReturnPipelineBehaviour<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(WithEitherPipelineBehaviour<,>));
// Build the service provider
var serviceProvider = services.BuildServiceProvider();

using var scope = serviceProvider.CreateScope();
var mediatr = scope.ServiceProvider.GetRequiredService<IMediator>();
await mediatr.Send(new RequestNoReturn());
await mediatr.Send(new RequestWithReturn());

internal record RequestNoReturn : IRequest
{
}
internal record RequestWithReturn : IRequest<int>
{
}

internal class RequestNoReturnHandler : IRequestHandler<RequestNoReturn>
{
    public Task Handle(RequestNoReturn notification, CancellationToken cancellationToken)
    {
        Console.WriteLine("WithoutReturn");
        return Task.CompletedTask;
    }
}
internal class RequestWithReturnHandler : IRequestHandler<RequestWithReturn, int>
{
    public Task<int> Handle(RequestWithReturn request, CancellationToken cancellationToken)
    {
        Console.WriteLine("WithReturn");
        return Task.FromResult(2);
    }
}

internal class NoReturnPipelineBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        Console.WriteLine($"{this.GetType()}  Before");

        var response = await next();
        Console.WriteLine($"{this.GetType()}  After");
        return response;
    }
}

internal class WithReturnPipelineBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        Console.WriteLine($"{this.GetType()}  Before");

        var response = await next();
        Console.WriteLine($"{this.GetType()}  After");
        return response;
    }
}

internal class WithEitherPipelineBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
    public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
    {
        Console.WriteLine($"{this.GetType()}  Before");

        var response = await next();
        Console.WriteLine($"{this.GetType()}  After");
        return response;
    }
}

This code returns

NoReturnPipelineBehaviour`2[RequestNoReturn,MediatR.Unit]  Before
WithEitherPipelineBehaviour`2[RequestNoReturn,MediatR.Unit]  Before
WithoutReturn
WithEitherPipelineBehaviour`2[RequestNoReturn,MediatR.Unit]  After
NoReturnPipelineBehaviour`2[RequestNoReturn,MediatR.Unit]  After
WithReturnPipelineBehaviour`2[RequestWithReturn,System.Int32]  Before
WithEitherPipelineBehaviour`2[RequestWithReturn,System.Int32]  Before
WithReturn
WithEitherPipelineBehaviour`2[RequestWithReturn,System.Int32]  After
WithReturnPipelineBehaviour`2[RequestWithReturn,System.Int32]  After
irpg7 commented 6 months ago

I don't think that's a clean approach to make/register 2 of same purpose pipelines, however i already know i can make it a single interface. What i tried to say is it's confusing since both are IRequest.

jbogard commented 6 months ago

Your pipeline behavior doesn't need that constraint. It's been changed to just notnull:

https://github.com/jbogard/MediatR/blob/master/src/MediatR/IPipelineBehavior.cs#L20

See also the upgrade guides about this change. That should fix everything for you.