jbogard / MediatR

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

IRequestExceptionHandler fails during startup procedure when Extenstion of IRequest interface is used as TRequest #938

Closed RazvanRus97 closed 11 months ago

RazvanRus97 commented 1 year ago

Hello, Get the following error when trying to use an extension of IRequest instead of a class/record: A host error has occurred during startup operation '10087f8a-99b3-4b69-953e-e85c2c297521'. [2023-07-28T13:25:26.247Z] Microsoft.Azure.WebJobs.Script.WebHost: Registering implementation type TestProject.TrackedCommandExceptionHandler is not assignable to service type MediatR.Pipeline.IRequestExceptionHandler<TestProject.TrackedCommand1, MediatR.Unit, Exception>. Value cannot be null. (Parameter 'provider')

MediatR version: 12.1.1

What am I trying to do: I have multiple commands that need to have their status tracked in a non-relational database, and I created a behavior and an exception handler(also tried with actions) that do these updates. The behavior works as expected but if I use IRequestExceptionAction the action is not triggered and if I use IRequestExceptionHandler I get the above-mentioned error. Also works just fine with records but not if I have a base record that I extend in my commands.

What the code looks like now: (Something like this)


public interface ITrackedRequest : IRequest 
{
  public TrackingData TrackingData { get; init; }
}
public record TrackedCommand(TrackingData TrackingData) : ITrackedRequest 

public sealed class TrackedCommandHandler: IRequestHandler<TrackedCommand>
{
    public async Task Handle(TrackedCommand request, CancellationToken cancellationToken)
    {
     // logic
    }
}
public sealed class TrackedCommandExceptionHandler : IRequestExceptionHandler<ITrackedRequest , Unit, Exception>
{
    private readonly ITableAdapter tableAdapter;

    public InboxTrackedRequestExceptionHandler(ITableAdapter tableAdapter) 
        => this.tableAdapter = tableAdapter;

    public async Task Handle(ITrackedRequest request, Exception exception, RequestExceptionHandlerState<Unit> state, CancellationToken cancellationToken)
    {
        await tableAdapter.SetFailedStatus(request.TrackingData , exception);
    }
}
internal class TrackedRequestSuccessBehavior : IPipelineBehavior<ITrackedRequest, Unit>
{
    private readonly ITableAdapter tableAdapter;

    public InboxTrackedRequestSuccessBehavior(ITableAdapter tableAdapter)
    {
        this.tableAdapter = tableAdapter;
    }

    public async Task<Unit> Handle(IInboxTrackedRequest request, RequestHandlerDelegate<Unit> next, CancellationToken cancellationToken)
    {
        await next();
        await tableAdapter.SetSuccessfulStatus(request.TrackingData );

        return Unit.Value;
    }
}

I am not sure if this is a MediatR error or if am i doing something wrong here. Thank you!

maik-hasler commented 1 year ago

Hi, could you please provide how you register your MediatR services?

RazvanRus97 commented 1 year ago
services.AddMediatR(cfg =>
{
            cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
            cfg.RegisterServicesFromAssemblyContaining<ITrackedRequest>();
            cfg.AddBehavior<IPipelineBehavior<ITrackedRequest, Unit>, TrackedRequestSuccessBehavior>();
}
);

Also, the project is an azure function project v4.

jbogard commented 11 months ago

Have you considered using generic constraints instead? Something like:

public sealed class TrackedCommandExceptionHandler : IRequestExceptionHandler<TTrackedRequest , Unit, Exception>
   where TTrackedRequest : ITrackedRequest
{
    private readonly ITableAdapter tableAdapter;

    public InboxTrackedRequestExceptionHandler(ITableAdapter tableAdapter) 
        => this.tableAdapter = tableAdapter;

    public async Task Handle(TTrackedRequest request, Exception exception, RequestExceptionHandlerState<Unit> state, CancellationToken cancellationToken)
    {
        await tableAdapter.SetFailedStatus(request.TrackingData , exception);
    }
}

The containers generally do better with open generics in cases like this because of issues with variance.