Search code examples
c#mediatr.net-7.0minimal-apisasp.net-core-7.0

.NET Core 7 Minimal API MediatR IRequest Handler mapping error


I am currently getting the following error when executing a command via .NET Core 7 Minimal API:

 Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: Error constructing handler for request of type MediatR.IRequestHandler`2[mNet.FileServer.Commands.Core.Application.Features.
StoredFileTypes.NewStoredFileType.NewStoredFileTypeCommand,mNet.Common.Core.Application.Commands.BaseCommandResponse`1[mNet.FileServer.Shared.Core.Domain.StoredFile
Types.ValueObjects.StoredFileTypeId]]. Register your handlers with the container. See the samples in GitHub for examples.
       ---> System.InvalidOperationException: No service for type 'MediatR.IRequestHandler`2[mNet.FileServer.Commands.Core.Application.Features.StoredFileTypes.NewS
toredFileType.NewStoredFileTypeCommand,mNet.Common.Core.Application.Commands.BaseCommandResponse`1[mNet.FileServer.Shared.Core.Domain.StoredFileTypes.ValueObjects.S
toredFileTypeId]]' has been registered.
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
         at MediatR.ServiceFactoryExtensions.GetInstance[T](ServiceFactory factory)
         at MediatR.Wrappers.HandlerBase.GetHandler[THandler](ServiceFactory factory)
         --- End of inner exception stack trace ---
         at MediatR.Wrappers.HandlerBase.GetHandler[THandler](ServiceFactory factory)
         at MediatR.Wrappers.RequestHandlerWrapperImpl`2.<>c__DisplayClass1_0.<Handle>g__Handler|0()
         at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken)
         at MediatR.Pipeline.RequestExceptionProcessorBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken)
         at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken)
         at MediatR.Pipeline.RequestExceptionActionProcessorBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken)
         at MediatR.Pipeline.RequestPostProcessorBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken)
         at MediatR.Pipeline.RequestPreProcessorBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken)
         at mNet.FileServer.Commands.Ui.MinimalApi.Endpoints.StoredFileTypeEndpoint.NewStoredFileTypeAsync(NewStoredFileTypeDto request, IMediator mediator) in C:\U
sers\dcmea\OneDrive\mNet Microservices\mNet.FileServer\mNet.FileServer.Commands.Ui.MinimalApi\Endpoints\StoredFileTypeEndpoint.cs:line 47
         at Microsoft.AspNetCore.Http.RequestDelegateFactory.<ExecuteTaskOfT>g__ExecuteAwaited|111_0[T](Task`1 task, HttpContext httpContext)
         at Microsoft.AspNetCore.Http.RequestDelegateFactory.<>c__DisplayClass89_2.<<HandleRequestBodyAndCompileRequestDelegateForJson>b__2>d.MoveNext()
      --- End of stack trace from previous location ---
         at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
         at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
         at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

The key parts of the code are in the following projects and are as follows:

  • mNet.Common.Core.Application: Contains the BaseCommand and BaseCommandResponse classes
  • mNet.FileServer.Shared.Core.Domain: Contains the StoredFileTypeId value object
  • mNet.FileServer.Commands.Core.Domain: Contains the StoredFileType aggregate
  • mNet.FileServer.Commands.Core.Application: Contains the NewStoredFileTypeCommand and NewStoredFileTypeCommandHandler classes
  • mNet.FileServer.Commands.Ui.MinimalApi: Contains the main Program.cs and minimal api endpoints

The key classes are:

Program.cs (extract)

builder.Services.AddMediatR(AppDomain.CurrentDomain.GetAssemblies());

Minimal Api Endpoint

private static async Task<BaseCommandResponse<StoredFileTypeId>> NewStoredFileTypeAsync(NewStoredFileTypeDto request, IMediator mediator)
    {
        var messageId = Guid.NewGuid();
        var command = new NewStoredFileTypeCommand
        {
            Id = new MessageId(messageId),
            CorrelationId = new CorrelationId(messageId),
            CausationId = new CausationId(messageId),
            CommandDto = request
        };

        var response = await mediator.Send(command); //Error happens after this MediatR call and is line 47 as referenced in the error
        return response;

    }

NewStoredFileTypeCommand

public class NewStoredFileTypeCommand : BaseCommand, IRequest<BaseCommandResponse<StoredFileTypeId>>
{
    public NewStoredFileTypeDto CommandDto { get; init; } = default!;
}

NewStoredFileTypeCommandHandler

public class NewStoredFileTypeCommandHandler : IRequestHandler<NewStoredFileTypeCommand, BaseCommandResponse<StoredFileTypeId>>
{
    private readonly IMapper _mapper;
    private readonly IEventSourcingHandler<StoredFileType, StoredFileTypeId> _eventSourcingHandler;

    public NewStoredFileTypeCommandHandler(IMapper mapper,
        IEventSourcingHandler<StoredFileType, StoredFileTypeId> eventSourcingHandler)
    {
        _mapper = mapper;
        _eventSourcingHandler = eventSourcingHandler;
    }

    public async Task<BaseCommandResponse<StoredFileTypeId>> Handle(NewStoredFileTypeCommand request, CancellationToken cancellationToken)
    {
       var response = new BaseCommandResponse<StoredFileTypeId>();

        var aggregate = new StoredFileType(
            new StoredFileTypeId(Guid.NewGuid()), 
            request.CorrelationId, 
            new CausationId(request.Id.Id),
            request.CommandDto.Name, 
            request.CommandDto.IsImageFileType,
            _mapper.Map<BootstrapIconCode>(request.CommandDto.BootstrapIconCode),
            _mapper.Map<MimeType>(request.CommandDto.MimeType)
            );
        
        await _eventSourcingHandler.SaveAsync(aggregate);

        response.Id = aggregate.Id;
        response.Message = $"Created new {nameof(StoredFileType)} aggregate!";
        response.IsSuccessful = true;

        return response;


    }

From doing a few debug traces, it seems like MediatR isn't mapping the NewStoredFileTypeCommandHandler to the NewStoredFileTypeCommand and I cannot work out why.

Also, as I have declared AppDomain.CurrentDomain.GetAssemblies() in Program.cs, it shouldn't be the scope.

Any ideas would be massively appreciated as it's driving me mad!


Solution

  • My guess relies on the following quote from the docs for AppDomain.GetAssemblies:

    Gets the assemblies that have been loaded into the execution context of this application domain.

    There is a chance that the corresponding assembly has not yet been loaded into the app domain. Try using the AddMediatr and providing assemblies/types from the assemblies storing mediatr parts:

    // use a type per assembly containing the MediatR components
    builder.Services.AddMediatR(typeof(NewStoredFileTypeCommandHandler), typeof(BaseCommand), ...);