Difference between revisions of "MediatR"

From Logic Wiki
Jump to: navigation, search
(Using in Controller)
 
Line 66: Line 66:
 
   public async Task<ActionResult<int>> CreatePlayer(CreatePlayerCommand command)
 
   public async Task<ActionResult<int>> CreatePlayer(CreatePlayerCommand command)
 
   {
 
   {
     var playerId = awsait _sender.Send(command);
+
     var playerId = await _sender.Send(command);
 
     return Ok(playerId);  
 
     return Ok(playerId);  
 
   }
 
   }
 
}
 
}
 
</pre>
 
</pre>
 +
 
== Query ==
 
== Query ==
 
=== No Parameter ===
 
=== No Parameter ===

Latest revision as of 12:37, 26 February 2026


Installation

Install Nuget Package of MediatR (by Jimmy Bogard)

In Program.cs register it. (it's Program assembly because it's the same application. )

builder.Services.AddMediatR(configuration => {
   configuration.RegisterServicesFromAssembly(typeof(Program).Assembly);
})

File - Folder Structure

Folders

create folders like

/Features/Players/Queries
/Features/Players/Commands

Files

In the Commands folder

CreatePlayerCommand.cs

using MediatR

public class CreatePlayerCommand : IRequest<int>
{
  public string Name {get; set;}
  public int Level {get; set;}
}

CreatePlayerCommandHandler.cs

using MediatR

public class CreatePlayerCommandHandler : IRequestHandler<CreatePlayerCommand, int>
{

  private readonly myDbContext _context;
  public CreatePlayerCommandHandler(MyDbContext context)
  {
    _context = context;
  }
  public Task<int> Handle(CreatePlayerCommand request, CancellationToken cancellationToken)
  {
    var player = new Player{Name = request.Name, Level=request.Level};
    _context.Players.Add(player);
    await _context.SaveChangesAsync();

    return player.Id;
  }
}

Using in Controller

using MediatR

public class PlayerController: ControllerBase
{
  private readonly ISender _sender
 
  public PlayerController(ISender sender)
  {
    _sender = sender
  }

  public async Task<ActionResult<int>> CreatePlayer(CreatePlayerCommand command)
  {
    var playerId = await _sender.Send(command);
    return Ok(playerId); 
  }
}

Query

No Parameter


public class WeatherQuery : IRequest<WeatherForecast[]>
{
  
}

public class WeatherQueryHandler : IRequestHandler<WeatherQuery ,WeatherForecast[]>
{
}

public async Task<ActionResult<IEnumerable<WeatherForecast>>>Get()
{
    var results = await _sender.Send(new WeatherQuery());
    return Ok(results);
}

With Parameter

public class SingleQuery : IRequest<WeatherForecast>
{
  public int RecordIndex { get; set; }   
}

public class SingleQueryHandler : IRequestHandler<SingleQuery, WeatherForecast>
{
}

public async Task<ActionResult<WeatherForecast>>Get(SingleQuery query)
{
    var results = await _sender.Send(query);
    return Ok(results);
}

Delete Command

Command

using MediatR;
public record DeleteItemCommand(int Id) : IRequest<bool>;

Command Handler

 using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Threading;
using System.Threading.Tasks;

public class DeleteItemCommandHandler : IRequestHandler<DeleteItemCommand, bool>
{
    private readonly AppDbContext _context;

    public DeleteItemCommandHandler(AppDbContext context)
    {
        _context = context;
    }

    public async Task<bool> Handle(DeleteItemCommand request, CancellationToken cancellationToken)
    {
        var item = await _context.Items.FirstOrDefaultAsync(i => i.Id == request.Id, cancellationToken);
        if (item == null)
            return false;

        _context.Items.Remove(item);
        await _context.SaveChangesAsync(cancellationToken);

        return true;
    }
}

Controller

using MediatR;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class ItemsController : ControllerBase
{
    private readonly IMediator _mediator;

    public ItemsController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpDelete("{id:int}")]
    public async Task<IActionResult> DeleteItem(int id)
    {
        var success = await _mediator.Send(new DeleteItemCommand(id));

        if (!success)
            return NotFound(new { Message = $"Item with id {id} not found." });

        return NoContent();
    }
}

Validation Pipeline

Fluent Validation Packages

Add these nuget packages

FluentValidation
FluentValidationDependencyInjectionExtensions

UseFluentValidationExceptionHandler

Create /Extensions/ApplicationBuilderExtensions.cs file

public static class ApplicationBuilderExtensions
{
  public static void UseFluentValidationExceptionHandler(this IApplicationBuilder app)
  {
    app.UseExceptionHandler(x => {
      x.Run(async context => {
        var errorFeature = context.Features.Get<IExceptionHandlerFeature>();
        var exception = errorFeature.Error;

        if(!(exception is ValidationException validationException))
        {
          throw exception;
        }

        var errors = validationExceptionErrors.Select(err =>
        {
          err.PropertyName,
          err.ErrorMessage
        });
        var errorText = JsonSerializer.Serialize(errors); 
        context.Response.StatusCode = 400;
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(errorText, Encoding.UTF8);
      });
    });
  }
}

CreatePlayerCommandValidation.cs

Create this file in the same folder with the CreatePlayerCommand.cs

using FluentValidation;

public class CreatePlayerCommandValidation : AbstractValidator<CreatePlayerCommand>
{
  public CreatePlayerCommandValidation()
  {
     RuleFor(x=> x.Name()
       .NotEmpty();
     RuleFor(x=> x.Level()
       .NotEmpty();
  }
}

Add ValidatiorsFromAssembly

in Startup.cs under ConfigureServices

services.AddValidatorsFromAssembly(typeof(Startup).Assembly);

PipelineBehaviour

Create this folder in the root of the project

/PipelineBehaviours 

Add ValidationBehaviour.cs

public class ValidationBehaviour<TRequest, TResponse>: IPipelineBehaviour<TRequest, TResponse>
   where TRequest : IRequest<TResponse>
{
  private readonly IEnumerable<IValidator<TRequest>> _validators;

  public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
  {
    _validators = validators;
  }
  public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
  {
    var context = new ValidationContext(request)
    var failures = _validators
      .Select(x=> x.Validate(context))
      .SelectMany(x=> x.Errors)
      .Where(x=> x != null)
      .ToList();

    if (failures.Any())
    {
       throw new ValidationException(failures)
    }

    return next();
  }
}

instead of TResponse here it can return a RetVal object. So we do not need to throw an exception here but return an object.

Registering Behaviour

in Startup.cs under ConfigureServices

services.AddTransient(typeof(IPipelineBehaviour<,>), typeof(ValidationBehaviour<,>));