3

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!

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
dmeadley
  • 77
  • 5

1 Answers1

3

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), ...);
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • Thanks - that worked perfectly! Is there a way to force the assemblies to load? It's going to be a pain to have to add that line for every command handler – dmeadley Feb 14 '23 at 10:01
  • @dmeadley - was glad to help! If answer works for you - feel free to mark it as accepted one) _" Is there a way to force the assemblies to load"_ - yes, you need to use the type from it). _"It's going to be a pain to have to add that line for every command handler"_ - AFAIK you don't need to, you need only one per assembly (as far as I remember Mediatr uses type to get it's assembly and load all needed Mediatr components). – Guru Stron Feb 14 '23 at 10:05
  • Thanks for that @Guru Stron. I'll see how I can use the type from it on loading to prevent needing to remember to add one per assembly – dmeadley Feb 14 '23 at 10:13
  • 1
    @dmeadley `typeof(SomeType)` - is actually a usage. I would recommend to stick to the code in answer because there would be no difference in terms of "needing to remember". And type usage for the sake of loading assembly before the `AddMediatR` is quite a brittle approach (relies on order, runtime behaviour, etc.). The `AddMediatR` with one type per assembly approach I would say is much cleaner and maintainable. – Guru Stron Feb 14 '23 at 10:18