0

Just dropping a question here as I've been trying to implement an optimisation of my code that seemed straightforward in my head, but that is proving more difficult than expected due to some MediatR shenanigans. Hoping somebody here can give me a nudge in the right direction.

Hoping to be able to use the same Mediator request to return multiple specialisations of the same basic DTO, I've implemented a generic type like so:

//query
public class GetByIdQuery<TReturnDto> : IRequest<TReturnDto> 
    where TReturnDto : BaseDto
{
    public Guid Id { get; set; }
}

//handler
public class GetByIdQueryHandler<TReturnDto> : IRequestHandler<GetByIdQuery<TReturnDto>, TReturnDto>
    where TReturnDto : BaseDto
{
    private readonly IRepository _repository;
    private readonly IMapper _mapper;

    public GetByIdQueryHandler(IRepository repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }

    public async Task<TReturnDto> Handle(GetByIdQuery<TReturnDto> request, CancellationToken cancellationToken)
    {
        var entity = await _repository.GetByIdAsync(request.Id, cancellationToken);

        return _mapper.Map<TReturnDto>(entity);
    }
}

//validator
public class GetByIdQueryValidator<TReturnDto> : AbstractValidator<GetByIdQuery<TReturnDto>>
    where T : BaseDto
{
    public GetByIdQueryValidator()
    {
        RuleFor(v => v.Id).ValidateGuid("Id is mandatory."); //custom extension method, not believed to be relevant to issue
    }
}

To me, this seemed like it should work. I had three existing unit tests, from before I attempted the genericisation, that looked like this:

[TestMethod]
public async Task Should_ThrowAnErrorForValidation()
{
    //Arrange
    var query = new GetByIdQuery<SpecialisedDto>();

    //Act
    //Assert
    await FluentActions.Invoking(() => SendAsync(query)).Should().ThrowAsync<Exception>();
}

[TestMethod]
public async Task Should_ReturnNull_ForInvalidGuid()
{
    //Arrange
    var query = new GetByIdQuery<SpecialisedDto>
    {
        Id = Guid.NewGuid(),
    };

    //Act
    var result = await SendAsync(query);

    //Assert
    //...
}

[TestMethod]
public async Task Should_ReturnEntity_OnMatchingId()
{
    //Arrange
    var id = new Guid("some-known-guid");
    var query = new GetByIdQuery<SpecialisedDto>
    {
        Id = id,
    };

    //Act
    var result = await SendAsync(query);

    //Assert
    //...
}

When I tried to run these unit tests, the initial result was that one testing the validation was successful, whereas the two testing the handler behaviour both reported that there was no MediatR handler for GetByIdQuery<SpecialisedDto>.

I did some digging and found this SO post, followed the advice and added the following line to my ConfigureServices.cs:

services.AddTransient<IRequestHandler<GetByIdQuery<SpecialisedDto>, SpecialisedDto>, GetByIdQueryHandler<SpecialisedDto>>();

Adding this line made the handler behave as expected, and both the associated unit tests pass. However, it seems to have played havoc with the validator, as the validator unit test is now failing, saying that it expected an Exception to be thrown but none were.

I presume that something further needs to be added to my ConfigureServices.cs to specify the validator that should be used, but I'm unsure as to what and can't find any examples. FWIW, here is the current state of my ConfigureServices.cs:

public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
    services.AddAutoMapper(Assembly.GetExecutingAssembly());
    services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
    services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
        
    services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
    services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidatorBehavior<,>));

    services.AddTransient<IRequestHandler<GetByIdQuery<SpecialisedDto>, SpecialisedDto>, GetByIdQueryHandler<SpecialisedDto>>();

    return services;
}

Any help on what might need to be tweaked to get this thing behaving itself is greatly appreciated, cheers!

Mark

marcuthh
  • 592
  • 3
  • 16
  • 42

0 Answers0