views:

40

answers:

0

I'm having a weird (at least to me) problem with validation using ViewModels and Html.EditorFor(T). The validation errors produced by <%: Html.ValidationSummary(false) %> lists properties which are not invalid, and do not correspond to the values in the ModelState dictionary when I view it in debug. I'm using strongly typed view models containing collections of look up values as well as the model objects themselves.

My guess is that it's something to do with how I'm nesting the view models, but at this point I can't be sure.


Edit

  1. I've boiled the problem down to the model binding. When the POST action is executed the ModelState already has the 3 PhoneNumberId errors.
  2. I stupidly forgot to mention that I'm using xVal for validation

I realise that that I've got about 5 or so model classes and so the source below is quite long, but I hope that it's readable and valuable to you.

In detail then: first I'll start with my controller actions.

public ActionResult Create()
{
    ClientStaffEditViewModel vm = new ClientStaffCreateViewModel(_repository.SelectStates(), new PersonCreateViewModel(_repository.SelectTitles()), null);
    return View(vm);
}

[HttpPost]
[ValidateInput(false)]
public ActionResult Create(ClientStaffEditViewModel vm)
{
    try {
        vm.ClientStaff.Person = vm.PersonEdit.Person; // This feels wrong
        ClientStaff newCs = _repository.Save(vm.ClientStaff);
        return RedirectToAction("Details", new { id = newCs.ClientStaffId });
    }
    catch (RulesException rEx) {
        rEx.AddModelStateErrors(ModelState, "ClientStaff");

        if (vm.PersonEdit.Titles == null) {
            vm.PersonEdit.Titles = _repository.SelectTitles();
        }

        if (vm.States == null) {
            vm.States = _repository.SelectStates();
        }

        return View(vm);
    }
}

These are the model classes:

public class ClientStaff
{
    public int ClientStaffId { get; set; }
    public Person Person { get; set; }
    public State State { get; set; }

    [Required]
    public string Mnemonic { get; set; }
}

public class Person
{
    public int PersonId { get; set; }
    public int TitleId { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address Address { get; set; }

    [Required]
    public DateTime DateOfBirth { get; set; }
    public PhoneNumber TelephoneHome { get; set; }
    public PhoneNumber TelephoneAdditional { get; set; }
    public PhoneNumber TelephoneMobile { get; set; }
}

public class Address
{
    public int AddressID { get; set; }
    public string NameOrNum { get; set; }
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public string City { get; set; }
    public string County { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; }
}

public class PhoneNumber
{
    public int PhoneNumberId { get; set; }
    public string InternationalCode { get; set; }
    public string Number { get; set; }
    public string Note { get; set; }
}

public class Title
{
    public int TitleID { get; set; }
    public string Name { get; set; }
}

public class State
{
    public int StateId { get; set; }
    public string Name { get; set; }
}

All pretty straightforward. Next the view models. I'm using view models partially because I need to render a couple of drop down lists in the view and so I wanted to pass strongly typed collections to the view.

public class ClientStaffEditViewModel
{
    public ClientStaffEditViewModel() { }

    public ClientStaffEditViewModel(IEnumerable<State> states, PersonEditViewModel personEdit, ClientStaff clientStaff)
    {
        States = states;
        PersonEdit = personEdit;
        ClientStaff = clientStaff;
    }

    public IEnumerable<State> States { get; set; }
    public PersonEditViewModel PersonEdit { get; set; }
    public ClientStaff ClientStaff { get; set; }
}

public class PersonEditViewModel
{
    public PersonEditViewModel() { }

    public PersonEditViewModel(IEnumerable<Title> titles, Person person)
    {
        Titles = titles;
        Person = person;
    }

    public Person Person { get; set; }
    public IEnumerable<Title> Titles { get; set; }
}

The guts of the main view page looks like this:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<ViewModels.ClientStaffCreateViewModel>" %>

<% using (Html.BeginForm()) {%>
    <%: Html.ValidationSummary(false)%>
    <%: Html.EditorForModel() %>
    <!-- Form controls removed for brevity -->
<% } %>

The Html.EditorForModel() method renders /Views/Shared/EditorTemplates/ClientStaffEditViewModel.ascx like you'd expect:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ViewModels.ClientStaffEditViewModel>" %>
<h2>Client Staff</h2>
    <p>
        <%: Html.LabelFor(m => m.ClientStaff.State)%>
        <%= Html.DropDownListFor(m => m.ClientStaff.State.StateId, Model.States.ToSelectList("StateID", "Name"))%>
    </p>
    <%: Html.EditorFor(m => m.ClientStaff) %>
<h2>Person</h2>
    <%: Html.EditorFor(m => m.PersonEdit, Model.PersonEdit) %>
<h2>Phone</h2>
    <h3>Home</h3>
    <%: Html.EditorFor(m => m.PersonEdit.Person.TelephoneHome)%>
    <h3>Mobile</h3>
    <%: Html.EditorFor(m => m.PersonEdit.Person.TelephoneMobile)%>
    <h3>Additional</h3>
    <%: Html.EditorFor(m => m.PersonEdit.Person.TelephoneAdditional)%>
<h2>Address</h2>
    <%: Html.EditorFor(m => m.PersonEdit.Person.Address)%>

The PersonEditViewModel.ascx partial view looks like this:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<ViewModels.PersonEditViewModel>" %>
<p>
    <%: Html.LabelFor(m => m.Titles) %>
    <%= Html.DropDownListFor(m => m.Person.TitleId, Model.Titles.ToSelectList("TitleID", "Name")) %>
</p>

<%: Html.EditorFor(m => m.Person) %>

... and finally, if you've made it thus far congratulations are in order. The validation errors that I'm getting displayed are:

  • The Date of birth field is required.
  • The PhoneNumberId field is required.
  • The PhoneNumberId field is required.
  • The PhoneNumberId field is required.

The 3 invalid PhoneNumberIdproperties are not [Required] - in fact none of the phone number properties are required.