Use FluentValidation to access the database validating more than one field

Viewed 2677

I learned how to use the fluent validator I would like to know if you could help me with a question. I have a personal system where in my controller the create post method has the following code:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create (TicketViewModel model)
{
    ICollection <Piece> pieces;
    try
    {
        if (ModelState.IsValid)
        {
            if (_ticketRepository.GetById (model.Id)! = null)
            {
                Console.WriteLine ("Ticket already exists!");
            }

            var _model = _mapper.Map <Ticket> (model);
            var count = _ticketRepository.FindAllByPiece (_model);

            if (count> 0)
            {
                ModelState.AddModelError ("", "This ticket already exists for this room. Check the piece, date and time.");
                pieces = _pieceRepository.FindAll ();
                model.Pieces = pieces;
                return View (model);
            }

            _ticketRepository.Insert (_model);
            model.Seats = RegistrationSeats (model.QuantityOfSeats);

            return RedirectToAction ("Index");
        }
        pieces = _pieceRepository.FindAll ();

        model.Pieces = pieces;

        return View (model);
    }
    catch (Exception ex)
    {
        Console.WriteLine (ex.Message);
        return View (model);
    }
}

Note that in the code above I have a part like this:

var count = _ticketRepository.FindAllByPiece (_model);    
if (count> 0)
{
    ModelState.AddModelError ("", "This ticket already exists for this room. Check the piece, date and time.");
    pieces = _pieceRepository.FindAll ();
    model.Pieces = pieces;
    return View (model);
}
  • This code snippet calls a service from the repository that checks if the model (ticket) passed has the same partId, date and schedule as the one already in the bank. If there is a record with these same values, he does not add the ticket until the person creates a different one than the one that already exists.
  • Here is the code of the service that accesses the bank doing this search:
public int FindAllByPiece(Ticket model)
{
    return _saleTheaterTicketsContext.Tickets.Include(x => x.Piece).Where(x => x.PieceId == model.PieceId && x.Date == model.Date && x.Schedule == model.Schedule).Count();
}
  • This works well and to show in the View the message I added in ModelState.AddModelError I do like this in View:
<strong class = "text-danger">@ Html.ValidationSummary (true)</strong>
  • My question is as I am using FluentValidation, I would like to know if there is any way to do this with it, so that the controller is not full of things. I did some research, but to no avail. I tried to instantiate the repository in the Validation class (which I will put the code below) but I don't know how to do the validation for these 3 fields together, passing a model to the repository. Would you help me?
  • Here is code from the Validation class using FluentValidator:
public class TicketViewModelValidator: AbstractValidator <TicketViewModel>
{
    public TicketViewModelValidator ()
    {
        RuleFor (x => x.Price)
            .NotEmpty (). WithMessage ("Enter the price")
            .GreaterThan (0) .WithMessage ("The price must be greater than 0.00");
        RuleFor (x => x.QuantityOfSeats)
            .NotEmpty (). WithMessage ("Enter the number of seats")
            .GreaterThanOrEqualTo (10) .WithMessage ("The number of seats must be at least 10");
        RuleFor (x => x.Date)
            .NotEmpty (). WithMessage ("Enter Date")
            .Must (ValidDate) .WithMessage ("Date must be greater than or equal to today");
        RuleFor (x => x.Schedule)
            .NotEmpty (). WithMessage ("Enter time");
        RuleFor (x => x.PieceId)
            .NotEmpty (). WithMessage ("Select the part");
    }

    public static bool ValidDate (DateTime date)
    {
        if (date.Date> = DateTime.Now.Date)
        {
            return true;
        }

        return false;
    }
}
1 Answers

Fluent validation handles this with the "Must" method. What follows is an simplification of what you need to do

  1. Pass in an instance of the _ticketRepository to the validator. This requires that you use dependency injection. I am assuming you are doing that in your controller. Your validator's constructor would be modified to look like this:

    public class TicketViewModelValidator: AbstractValidator <TicketViewModel>
    {
        private ITiekctRepository _ticketRepository;
    
        public TicketViewModelValidator (ITicketRepository ticketRepository)
        {
            _ticketRepository = ticketRepository;
            //...rules go here
        }
  1. Modify your rules to check the repository for your conditions. For example, you might do something like:
RuleFor(t => t.Id).NotEmpty().Must(BeUnique).WithMessage("A ticket must have an ID that is unique in the database").

Then define a "BeUnique" method like this

    private bool BeUnique(TicketViewModelTicketViewModel instance)
    {
         // The instance is the model being validated.
         if (_ticketRepository.GetById (instance.Id) == null)
             return true
         return false;
    }

There is a lot of flexibility here so read the documents carefully. Note that since you are working on the model (called instance in the method) you have access to all properties and can check more than one thing at a time. You probably will not want to do that though, because it makes it harder to return a good error message. This technique works well with Entity Framework, since the data object referred to by model.Id will be cached in the dbContext and reused later if needs, so it save's a query.

Please note that I've typed a lot of this in from memory without the use of a compiler or Intellisense to get the grammar right. The basic flow should work though.

Related