5
votes

I have ViewModel that has inside a Model and some extra properties. There are validation on the model and on the property, but when executed, only validation on Model are checked, validation in the property is ignored.

The Model:

 [MetadataType(typeof(Customer_Validation))]
 public partial class Customer
 {
 }

 public class Customer_Validation
 {
     [Required(ErrorMessage="Please enter your First Name")]
     public string FirstName { get; set; }

     [Required(ErrorMessage = "Please enter your Last name")]
     public string LastName { get; set; }

     [Required(ErrorMessage = "Sorry, e-mail cannot be empty")]
     [Email(ErrorMessage="Invalid e-mail")]
     public string Email { get; set; }
 }

The ViewModel

 public class RegisterViewModel
 {
     public Customer NewCustomer { get; private set; }

     [Required(ErrorMessage="Required")]
     public string Password { get; private set; }

     public RegisterViewModel(Customer customer, string password)
     {
         NewCustomer = customer;
         Password = password;
     }
 }

The Controller

public ActionResult Create()
{
     Customer customer = new Customer();    
     RegisterViewModel model = new RegisterViewModel(customer, "");    
     return View(model);
}

[HttpPost]
public ActionResult Create(Customer newCustomer, string password)
{
     if (ModelState.IsValid)
     {
         try
         {
             // code to save to database, redirect to other page
         }
         catch
         {
             RegisterViewModel model = new RegisterViewModel(newCustomer, password);
             return View(model);
         }
     }
     else
     {
         RegisterViewModel model = new RegisterViewModel(newCustomer, password);
         return View(model);
     }
}

The View

@using (Html.BeginForm())
 {
  <table>
   <tr>
    <td>First Name:</td>
    <td>@Html.TextBoxFor(m => m.NewCustomer.FirstName)</td>
    <td>@Html.ValidationMessageFor(m => m.NewCustomer.FirstName)</td>
   </tr>
   <tr>
    <td>Last Name:</td>
    <td>@Html.TextBoxFor(m => m.NewCustomer.LastName)</td>
    <td>@Html.ValidationMessageFor(m => m.NewCustomer.LastName)</td>
   </tr>
   <tr>
    <td>E-mail:</td>
    <td>@Html.TextBoxFor(m => m.NewCustomer.Email)</td>
    <td>@Html.ValidationMessageFor(m => m.NewCustomer.Email)</td>
   </tr>
   <tr>
    <td>Password:</td>
    <td>@Html.TextBoxFor(m => m.Password)</td>
    <td>@Html.ValidationMessageFor(m => m.Password)</td>
   </tr>
  </table>

  <input type="submit" value="Register" />
 }

If I submit the form leaving Password empty it let go through. If I leave empty the Customer fields it do show the errors (except for the password field)

2

2 Answers

11
votes

That's normal. Your POST controller action takes Customer as parameter and not the view model. The validation is performed by the model binder, so when the model binder tries to bind the Customer object from request parameters it will invoke the validation. Your POST action needs to take the view model as parameter if you want the validation to be performed on this view model. Currently all you do with this view model inside the post action is to call the constructor and calling the constructor doesn't trigger any validation at all.

So your POST action should become:

[HttpPost]
public ActionResult Create(RegisterViewModel newCustomer)
{
     if (ModelState.IsValid)
     {
         // code to save to database, redirect to other page
     }
     else
     {
         return View(newCustomer);
     }
}

Notice how you don't need to pass the password as second action argument as it is already part of your view model.

0
votes

I believe this is because the setter on your password property is set to private set. All the setters on the rest of your customer fields are public setters. When a property does not have a setter inspection for a valid value is not required.