karolinabento
karolinabento

Reputation: 71

Use FluentValidation to access the database validating more than one field

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);
}
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();
}
<strong class = "text-danger">@ Html.ValidationSummary (true)</strong>
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;
    }
}

Upvotes: 6

Views: 10897

Answers (1)

GlennSills
GlennSills

Reputation: 4187

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.

Upvotes: 8

Related Questions