0

I have the following ViewModel:

 public class MyViewModel:IValidatableObject
    {
        public int Id { get; set; }

        public string Name { get; set; }

        public DateTime? Birthday { get; set; }

        public int Status { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            if (string.IsNullOrWhiteSpace(Name))
                yield return new ValidationResult("Please fill the name", new string[] { "Name" });

            if (Birthday.HasValue == false)
                yield return new ValidationResult("Please fill the birthday", new string[] { "Birthday" });

            if(Status <= 0)
                yield return new ValidationResult("Please fill the status", new string[] { "Status" });
        }
    }

Controller:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name,Birthday,Status")] MyViewModel myViewModel)
{
    if (ModelState.IsValid)
    {
        db.MyViewModels.Add(myViewModel);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    return View(myViewModel);
}

I would like to display all the validation messages at the same time, however it shows first status and then the other two properties.

enter image description here

enter image description here

Thiago Custodio
  • 17,332
  • 6
  • 45
  • 90

1 Answers1

1

This is due to the order in which validation happens. First the ModelBinder does it's job, and if that passes, since you've created a self validating viewmodel by implementing IValidatableObject, the Validate method is called. In the first screenshot the modelbinding process is failing so Validate is never called. In the second screenshot, modelbinding succeeds, but Validate() fails.

You can solve this by using DataAnnotations instead of implementing IValidatableObject like so:

    public class MyViewModel:IValidatableObject
    {
        public int Id { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        public DateTime Birthday { get; set; }
        [Required, Range(0, Int32.MaxValue)]
        public int Status { get; set; }
    }
joelmdev
  • 11,083
  • 10
  • 65
  • 89
  • Is it possible to achieve the same result keeping IValidatableObject? – Thiago Custodio Dec 30 '14 at 20:17
  • If you did all your validation through the Validate() method, yes. Just make all the properties on the viewmodel optional and write your rules in Validate(). – joelmdev Dec 30 '14 at 20:20
  • changing to public int? Status { get; set; } It displays only Name and Birthday validation messages. Ps: All my validation is done using Valida from IValidatableObject – Thiago Custodio Dec 30 '14 at 20:24
  • Right, but at that point you'd need to check Status.HasValue and manually add validation errors yourself just as you're currently doing for the Birthday property. – joelmdev Dec 30 '14 at 20:26
  • it's almost working. My only problem is when I change to Html.DropDownListFor(x=>x.Status) for example. It still validating Status first – Thiago Custodio Dec 30 '14 at 20:37
  • Maybe set a default value of -1 for your dropdownlist, something similar to the following: http://stackoverflow.com/questions/7229626/dropdownlistfor-default-value – joelmdev Dec 30 '14 at 21:03