C
C#12mo ago
_vegabyte_

❔ MediatR Issue with IRequest and Generic Method in ASP.NET Core

I'm facing an issue with MediatR in my ASP.NET Core project. I have an action method in a controller where I'm using MediatR's ISender.Send<TRequest> (where TRequest is GetCollectionsAsyncQuery) inside a generic method. However, I'm getting this exception: "The type 'GetCollectionsAsyncQuery' cannot be used as type parameter 'TRequest' in the generic type or method 'ISender.Send (TRequest, CancellationToken)'. There is no implicit reference conversion from 'GetCollectionsAsyncQuery' to 'IRequest'." Here's my HttpGet action method:
[HttpGet(Name = "GetCollectionAsync")]
public async Task<IActionResult> GetCollectionsAsync()
{
return await Handle<IEnumerable<GetCollectionsAsync.GetCollectionsAsyncQueryResult>>(
new GetCollectionsAsync.GetCollectionsAsyncQuery());
}
[HttpGet(Name = "GetCollectionAsync")]
public async Task<IActionResult> GetCollectionsAsync()
{
return await Handle<IEnumerable<GetCollectionsAsync.GetCollectionsAsyncQueryResult>>(
new GetCollectionsAsync.GetCollectionsAsyncQuery());
}
The relevant codes inside my Handler method and GetCollectionsAsyncQuery are:
public class GetCollectionsAsyncQuery : IRequest<IEnumerable<GetCollectionsAsyncQueryResult>> { }
//...
public class Handler : IRequestHandler<GetCollectionsAsyncQuery, IEnumerable<GetCollectionsAsyncQueryResult>>
{
var collections = await _context.Collections
.Include(group => group.Groups)
.ThenInclude(group => group.Payers)
.ThenInclude(payer => payer.Contributions)
.ToListAsync(cancellationToken);

if (collections == null) throw new NoCollectionFoundExceptions();
return _mapper.Map<IEnumerable<GetCollectionsAsyncQueryResult>>(collections);
}
public class GetCollectionsAsyncQuery : IRequest<IEnumerable<GetCollectionsAsyncQueryResult>> { }
//...
public class Handler : IRequestHandler<GetCollectionsAsyncQuery, IEnumerable<GetCollectionsAsyncQueryResult>>
{
var collections = await _context.Collections
.Include(group => group.Groups)
.ThenInclude(group => group.Payers)
.ThenInclude(payer => payer.Contributions)
.ToListAsync(cancellationToken);

if (collections == null) throw new NoCollectionFoundExceptions();
return _mapper.Map<IEnumerable<GetCollectionsAsyncQueryResult>>(collections);
}
16 Replies
_vegabyte_
_vegabyte_12mo ago
My base controller's Handle methods are:
protected async Task<IActionResult> Handle<T2, T3>(dynamic dto) {

var queryOrCommmand = _mapper.Map<T2>(dto);

return await Handle<T3>(queryOrCommmand);
}

protected async Task<IActionResult> Handle<T>(dynamic queryOrCommmand) {
if (queryOrCommmand == null)
return BadRequest();

var result = new QueryOrCommandResult<T>();
if (ModelState.IsValid)
{
try
{
result.Data = await _mediator.Send(queryOrCommmand);
result.Success = true;
}
catch (Exception ex)
{
result.Messages.Add(ex.Message);
}
}
else {
result.Messages = ModelState.Values.SelectMany(m => m.Errors)
.Select(e => e.ErrorMessage)
.ToList();
}

if (result.Success)
return Ok(result);
else
return BadRequest(result);
}
protected async Task<IActionResult> Handle<T2, T3>(dynamic dto) {

var queryOrCommmand = _mapper.Map<T2>(dto);

return await Handle<T3>(queryOrCommmand);
}

protected async Task<IActionResult> Handle<T>(dynamic queryOrCommmand) {
if (queryOrCommmand == null)
return BadRequest();

var result = new QueryOrCommandResult<T>();
if (ModelState.IsValid)
{
try
{
result.Data = await _mediator.Send(queryOrCommmand);
result.Success = true;
}
catch (Exception ex)
{
result.Messages.Add(ex.Message);
}
}
else {
result.Messages = ModelState.Values.SelectMany(m => m.Errors)
.Select(e => e.ErrorMessage)
.ToList();
}

if (result.Success)
return Ok(result);
else
return BadRequest(result);
}
Things I've Tried I've ensured GetCollectionsAsyncQuery implements IRequest<IEnumerable<GetCollectionsAsyncQueryResult>>. My Handler class processes the GetCollectionsAsyncQuery and returns a Task<IEnumerable<GetCollectionsAsyncQueryResult>>. I've checked that MediatR and AutoMapper are correctly registered in the dependency injection container during my application's startup. I have checked and confirmed that my AutoMapper configuration is fine. My mapping process maps the Collection entity to GetCollectionsAsyncQueryResult and it works correctly. I verified there's no issue with my MediatR pipeline behaviours or pre/post processors. Despite trying these things, the exception persists. Could anyone help me troubleshoot this?
Unknown User
Unknown User12mo ago
Message Not Public
Sign In & Join Server To View
_vegabyte_
_vegabyte_12mo ago
To have more flexibility and to be more reusable due to the ability to handle any type of object
Unknown User
Unknown User12mo ago
Message Not Public
Sign In & Join Server To View
_vegabyte_
_vegabyte_12mo ago
I think so. My goal here is to have a generic result for all of my actions instead of calling QueryorCommandResult new Instance every action
_vegabyte_
_vegabyte_12mo ago
GitHub
GitHub - aldrin0408av/MineralWaterMonitoringSystem
Contribute to aldrin0408av/MineralWaterMonitoringSystem development by creating an account on GitHub.
_vegabyte_
_vegabyte_12mo ago
This is first approach I use
[HttpPost("AddContribution")]
public async Task<IActionResult> AddContribution(AddContribution.AddContributionCommand command)
{
var response = new QueryOrCommandResult<Unit>();
try
{
var result = await _mediator.Send(command);
response.Success = true;
response.Messages.Add("Contribution is made successfully");
return CreatedAtRoute("GetContributionsAsync", result);
}
catch (Exception e)
{
response.Success = false;
response.Messages.Add(e.Message);
return Conflict(response);
}
}
[HttpPost("AddContribution")]
public async Task<IActionResult> AddContribution(AddContribution.AddContributionCommand command)
{
var response = new QueryOrCommandResult<Unit>();
try
{
var result = await _mediator.Send(command);
response.Success = true;
response.Messages.Add("Contribution is made successfully");
return CreatedAtRoute("GetContributionsAsync", result);
}
catch (Exception e)
{
response.Success = false;
response.Messages.Add(e.Message);
return Conflict(response);
}
}
SwaggerLife
SwaggerLife12mo ago
When It comes to MediatR, I don't recommend using generics. The only way mediatr is able to detect the correct handler is by the command/query. If you want to use generics, than you are going to have to register them correctly in your service collection. Have you done that? Don't use dynamic. Here is an example of how you can register your generics.
builder.Services.AddTransient<IRequestHandler<DislikeCommand<Comment>, bool>, DislikeHandler<Comment>>();
builder.Services.AddTransient<IRequestHandler<DislikeCommand<Comment>, bool>, DislikeHandler<Comment>>();
_vegabyte_
_vegabyte_12mo ago
What is the BaseController looks like?
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly, typeof(GetContributionsAsync.GetContributionsAsyncQuery).Assembly);
});
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblies(typeof(Program).Assembly, typeof(GetContributionsAsync.GetContributionsAsyncQuery).Assembly);
});
Something like this?
Unknown User
Unknown User12mo ago
Message Not Public
Sign In & Join Server To View
_vegabyte_
_vegabyte_12mo ago
how can I make a custom response like this one
protected async Task<IActionResult> Handle<T2, T3>(dynamic dto) {

var queryOrCommmand = _mapper.Map<T2>(dto);

return await Handle<T3>(queryOrCommmand);
}

protected async Task<IActionResult> Handle<T>(dynamic queryOrCommmand) {
if (queryOrCommmand == null)
return BadRequest();

var result = new QueryOrCommandResult<T>();
if (ModelState.IsValid)
{
try
{
result.Data = await _mediator.Send(queryOrCommmand);
result.Success = true;
}
catch (Exception ex)
{
result.Messages.Add(ex.Message);
}
}
else {
result.Messages = ModelState.Values.SelectMany(m => m.Errors)
.Select(e => e.ErrorMessage)
.ToList();
}

if (result.Success)
return Ok(result);
else
return BadRequest(result);
}
protected async Task<IActionResult> Handle<T2, T3>(dynamic dto) {

var queryOrCommmand = _mapper.Map<T2>(dto);

return await Handle<T3>(queryOrCommmand);
}

protected async Task<IActionResult> Handle<T>(dynamic queryOrCommmand) {
if (queryOrCommmand == null)
return BadRequest();

var result = new QueryOrCommandResult<T>();
if (ModelState.IsValid)
{
try
{
result.Data = await _mediator.Send(queryOrCommmand);
result.Success = true;
}
catch (Exception ex)
{
result.Messages.Add(ex.Message);
}
}
else {
result.Messages = ModelState.Values.SelectMany(m => m.Errors)
.Select(e => e.ErrorMessage)
.ToList();
}

if (result.Success)
return Ok(result);
else
return BadRequest(result);
}
Unknown User
Unknown User12mo ago
Message Not Public
Sign In & Join Server To View
_vegabyte_
_vegabyte_12mo ago
Thank you for your detailed feedback and suggestions concerning the use of static typing, direct mappings, and the single responsibility principle in our codebase. Your inputs are valuable and appreciated. For the extension does it look like this? Instead of auto mapper?
public static class CollectionExtensions
{
public static IEnumerable<GetCollectionsAsyncQueryResult> ToQueryResult(this IEnumerable<Collection> collections)
{
return collections.Select(c => new GetCollectionsAsyncQueryResult
{
Id = c.Id,
CreatedAt = c.CreatedAt,
GroupName = c.GroupName,
CollectionAmount = c.CollectionAmount,
CollectedAmount = c.CollectedAmount,
});
}
}
public static class CollectionExtensions
{
public static IEnumerable<GetCollectionsAsyncQueryResult> ToQueryResult(this IEnumerable<Collection> collections)
{
return collections.Select(c => new GetCollectionsAsyncQueryResult
{
Id = c.Id,
CreatedAt = c.CreatedAt,
GroupName = c.GroupName,
CollectionAmount = c.CollectionAmount,
CollectedAmount = c.CollectedAmount,
});
}
}
Unknown User
Unknown User12mo ago
Message Not Public
Sign In & Join Server To View
SwaggerLife
SwaggerLife12mo ago
No, keep the register services from assembly seperate.
builder.Services.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
builder.Services.AddMediatR(x => x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
Let's say we have this generic command
public record LikeCommand<T>(Guid RecordId) : IRequest<bool>;
public record LikeCommand<T>(Guid RecordId) : IRequest<bool>;
And here is the handler for it
public class LikeHandler<T> : IRequestHandler<LikeCommand<T>, bool>
{
public async Task<bool> Handle(LikeCommand<T> request, CancellationToken cancellationToken)
{
throw new NotImplementedException()
}
}
public class LikeHandler<T> : IRequestHandler<LikeCommand<T>, bool>
{
public async Task<bool> Handle(LikeCommand<T> request, CancellationToken cancellationToken)
{
throw new NotImplementedException()
}
}
Here is how you register it
builder.Services.AddTransient<IRequestHandler<LikeCommand<Quote>, bool>, LikeHandler<Quote>>();
builder.Services.AddTransient<IRequestHandler<LikeCommand<Comment>, bool>, LikeHandler<Comment>>();
builder.Services.AddTransient<IRequestHandler<LikeCommand<Quote>, bool>, LikeHandler<Quote>>();
builder.Services.AddTransient<IRequestHandler<LikeCommand<Comment>, bool>, LikeHandler<Comment>>();
You will need to register each type. So, I'm using the generic LikeCommand<T> for Quote and Comment. Then you have to explicitly register the types so that MediatR is able to locate the correct handler. The Boolean in the registration part. Indicates, they response the commend is going to return when the operation has been performed.
Accord
Accord12mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.