ESSENCE
Question: Why a custom validation error message is not displayed when using ViewModel.
Answer: Custom validation should apply to the ViewModel, not the class. See End of @ JaySilk84 answer, for example code.
MVC3 project using
- Jquery-1.7.2.min.js
- Modernizr-2.5.3.js
- jquery-ui-1.8.22.custom.min.js (generated by jQuery.com for the Accordion plugin)
- jquery.validate.min.js and
- jquery.validate.unobtrusive.min.js
I have a validation that works in my project for both data and ModelState.AddModelError in the controller, so I know that I set up the validation code correctly.
But during user verification, an error is generated in the code, but the error message is not displayed.
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be 18 or over."); } }
A debugging failure in the POST action of a user check leads to a failure of the model state and the error message is placed in the corresponding value field, but when the model is sent back to the view, the error message is not displayed. In the controller, I also have ModelState.AddModelError code, and its message is displayed. How will this work otherwise than with one, and not with the other? If this is not the case, what else could prevent the error message from being displayed?
Update 1:
I am using ViewModel to create a model in a view. I uninstalled the ViewModel and an error message appeared as soon as I added that the ViewModel again in the message again stopped showing. Has anyone successfully used custom validation with ViewModel? Was there anything you needed to do to make it work?
Update 2:
I created a new MVC3 project with these two simple classes (Agency and Person).
public class Agency : IValidatableObject { public int Id { get; set; } public string Name { get; set; } public DateTime DOB { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18."); } } } public class Person { public int Id { get; set; } public string Name { get; set; } }
Here is the controller code
public ActionResult Create() { return View(); } // // POST: /Agency/Create [HttpPost] public ActionResult Create(Agency agency) { if (ModelState.IsValid) { db.Agencies.Add(agency); db.SaveChanges(); return RedirectToAction("Index"); } return View(agency); } //[HttpPost] //public ActionResult Create(AgencyVM agencyVM) //{ // if (ModelState.IsValid) // { // var agency = agencyVM.Agency; // db.Agencies.Add(agency); // db.SaveChanges(); // return RedirectToAction("Index"); // } // return View(agencyVM); //}
View
@model CustValTest.Models.Agency @*@model CustValTest.Models.AgencyVM*@ @* When using VM (model => model.Name) becomes (model => model.Agency.Name) etc. *@ @{ ViewBag.Title = "Create"; } <h2>Create</h2> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>Agency</legend> <div class="editor-label"> @Html.LabelFor(model => model.Name) </div> <div class="editor-field"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) </div> <div class="editor-label"> @Html.LabelFor(model => model.DOB) </div> <div class="editor-field"> @Html.EditorFor(model => model.DOB) @Html.ValidationMessageFor(model => model.DOB) </div> <p> <input type="submit" value="Create" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index") </div>
ViewModel
public class AgencyVM { public Agency Agency { get; set; } public Person Person { get; set; } }
When only the Agency is displayed, the view shows a validation error (DOB under 18 years old). When presenting ViewModel, an error is not displayed. Custom validation always catches the error, although it causes a ModelState.IsValid error, and the view must be resubmitted. Can anyone repeat this? Any ideas on why and how to fix it?
Update 3:
As a temporary work, I changed Validation to one level level (compared to the model level) by adding a parameter to the ValidationResult:
if (DOB > DateTime.Now.AddYears(-18)) { yield return new ValidationResult("Must be over 18.", new [] { "DOB" }); }
The problem with this is that now the error message appears next to the field, and not at the top of the form (which does not say very well about the accordion, since the user will be returned to the form without a visible error message). To fix this secondary problem, I added this code to the POST action of the controller.
ModelState.AddModelError(string.Empty, errMsgInvld); return View(agencyVM); } string errMsgInvld = "There was an entry error, please review the entire form. Invalid entries will be noted in red.";
The question still remains unanswered, why the model level error message is not displayed using ViewModel (see my answer on JaySilk84 for more information about this)?