title: MVC3 Hierarchical View Model with check boxes and radio buttons
body: <p>I'm at the point of pulling what little hair I have left out - been learning MVC over the past few months, so I'm relatively new to it, though I've covered a lot of ground...</p>
  
  <p>I'm having trouble trying to output check boxes and radio buttons with a relatively complex view model.</p>
  
  <p>I have the following View (irrelevant fields removed):</p>
  
  <pre><code>@model CreateMediumModel
  @using(Html.BeginForm()) {
                  &lt;div class="formField"&gt;
                      &lt;div class="label"&gt;
                          @Html.LabelFor(m =&gt; m.MetaData)
                      &lt;/div&gt;
                      &lt;div class="input"&gt;
                          @Html.EditorFor(m =&gt; m.MetaData)
                      &lt;/div&gt;
                      @Html.ValidationMessageFor(m =&gt; m.MetaData)
                  &lt;/div&gt;
  
  }
  </code></pre>
  
  <p>The hierarchical View Model looks like this:</p>
  
  <pre><code>public class CreateMediumModel {
      public List&lt;MetaData&gt; MetaData { get; set; }
  }
  
  public class MetaData {
      /// &lt;summary&gt;
      /// The media type that this meta data belongs to.
      /// &lt;/summary&gt;
      public int MediaTypeID { get; set; }
  
      /// &lt;summary&gt;
      /// The unique ID for the meta data.
      /// &lt;/summary&gt;
      public int MetaDataID { get; set; }
  
      /// &lt;summary&gt;
      /// The unique text-based identifier for the meta data. Unique per MediaType.
      /// &lt;/summary&gt;
      public string Key { get; set; }
  
      /// &lt;summary&gt;
      /// The UI description of the meta data.
      /// &lt;/summary&gt;
      public string Description { get; set; }
  
      /// &lt;summary&gt;
      /// The max length of text based data.
      /// &lt;/summary&gt;
      public int Length { get; set; }
  
      /// &lt;summary&gt;
      /// Provides a pre-defined list of values for the meta data.
      /// &lt;/summary&gt;
      public List&lt;MetaDataOption&gt; Options { get; set; }
  
      /// &lt;summary&gt;
      /// Provides the text-based value for the meta data.
      /// &lt;/summary&gt;
      public string Value { get; set; }
  
      /// &lt;summary&gt;
      /// Determines which value/s to use from the object graph
      /// &lt;/summary&gt;
      public MetaDataDataType Type { get; set; }
  
      /// &lt;summary&gt;
      /// Defines the UI sort order of the meta data
      /// &lt;/summary&gt;
      public int SortOrder { get; set; }
  }
  
  public class MetaDataDataType {
      public int MetaDataTypeID { get; set; }
      public MetaDataDataTypeEnum Type { get; set; }
      public bool TextBased { get; set; }
  }
  
  public class MetaDataOption {
      /// &lt;summary&gt;
      /// The unique ID for the option
      /// &lt;/summary&gt;
      public int MetaDataOptionID { get; set; }
  
      /// &lt;summary&gt;
      /// The pre-defined option value to display.
      /// &lt;/summary&gt;
      public string Value { get; set; }
  
      /// &lt;summary&gt;
      /// The sort order of the value in the list.
      /// &lt;/summary&gt;
      public int SortOrder { get; set; }
  }
  
  public enum MetaDataEnum {
      Developer,
      PEGI,
      PEGIContent
  }
  
  public enum MetaDataDataTypeEnum {
      /// &lt;summary&gt;
      /// Simple text
      /// &lt;/summary&gt;
      TEXT = 1,
  
      /// &lt;summary&gt;
      /// Multi-line text.
      /// &lt;/summary&gt;
      MULTITEXT = 2,
  
      /// &lt;summary&gt;
      /// Simple number
      /// &lt;/summary&gt;
      NUMBER = 3,
  
      /// &lt;summary&gt;
      /// Simple date
      /// &lt;/summary&gt;
      DATE = 4,
  
      /// &lt;summary&gt;
      /// Simple time
      /// &lt;/summary&gt;
      TIME = 5,
  
      /// &lt;summary&gt;
      /// Simple DateTime
      /// &lt;/summary&gt;
      DATETIME = 6,
  
      /// &lt;summary&gt;
      /// Single option selection
      /// &lt;/summary&gt;
      SELECTION = 7,
  
      /// &lt;summary&gt;
      /// Multi option selection
      /// &lt;/summary&gt;
      MULTISELECTION = 8
  }
  </code></pre>
  
  <p>Each piece of meta data has a single type; the type defines how the meta data is edited and how it's displayed (and where the data is stored in the data layer). The display side of things is working perfectly fine.</p>
  
  <p>When the type is "SELECTION", I want to display a single set of radio buttons, where only one can be selected. When the type is "MULTISELECTION", I want to display a set of checkboxes, where more than one can be selected. I can do this, but I've been able to successfully bind the values selected to the model.</p>
  
  <p>At the moment, I'm using an editor template to display the List, which looks like this:</p>
  
  <pre><code>@model MetaData
  @using Business.Enumerations
  
  @if(Model != null) {
      @Html.HiddenFor(m =&gt; m.MetaDataID)
      &lt;div class="metaData@(Model.MediaTypeID) checkbox"&gt;
          &lt;div class="metaDescription"&gt;
              @Model.Description
          &lt;/div&gt;
          &lt;div class="metaValue"&gt;
              @switch(Model.Type.Type) {
                  case MetaDataDataTypeEnum.NUMBER:
                  case MetaDataDataTypeEnum.DATE:
                  case MetaDataDataTypeEnum.TEXT:
                  case MetaDataDataTypeEnum.TIME:
                  case MetaDataDataTypeEnum.DATETIME:
                      @Html.TextBoxFor(m =&gt; m.Value)
                      break;
                  case MetaDataDataTypeEnum.MULTITEXT:
                      @Html.TextAreaFor(m =&gt; m.Value)
                      break;
                  case MetaDataDataTypeEnum.SELECTION:
                      foreach(var o in Model.Options) {
                          @Html.RadioButtonFor(m =&gt; m.Value, o.MetaDataOptionID) @o.Value
                      }
                      break;
                  case MetaDataDataTypeEnum.MULTISELECTION:
                      foreach(var o in Model.Options) {
                          @Html.CheckBoxFor(m =&gt; m.Options.FirstOrDefault(opt =&gt; opt.Selected).Selected) @o.Value
                      }
                      break;
     }
          &lt;/div&gt;
      &lt;/div&gt;
  }
  </code></pre>
  
  <p>This will be expanded when I get over this issue, but for now, it outputs everything as expected - I see a radio button list for SELECTION types and a checkbox list for MULTISELECTION types, but it's not bound to the model properly when I call the submit action.</p>
  
  <p>I'm going a bit crazy. Can anyone see anything obvious I'm doing wrong?</p>
  
  <p>As an aside:</p>
  
  <p>I've tried to create an editor template for the MetaDataOption class too, but there seems to be a bug surrounding passing a List into a sub-editor template, when a template name is specified (Html.EditorFor(m => m.Options, "MetaDataOptionsSingle") and Html.EditorFor(m => m.Options, "MetaDataOptionsMulti")) - if I don't specify a name for the editor template, the template can have a MetaDataOption model, but if a specify a name, it requires a List.</p>
  
  <hr/>
  
  <p><strong>UPDATE</strong></p>
  
  <p>OK, I've managed to get this working, but I'm having problems with the unobtrusive validation of checkboxes now. The fix is as follows:</p>
  
  <p>The MetaData editor template now looks like this:</p>
  
  <pre><code>@model MetaData
  @using Business.Enumerations
  
  @if(Model != null) {
      @Html.HiddenFor(m =&gt; m.Description)
      @Html.HiddenFor(m =&gt; m.Type)
      &lt;div class="metaData@(Model.MediaTypeID) checkbox"&gt;
          &lt;div class="metaDescription"&gt;
              @Model.Description
          &lt;/div&gt;
          &lt;div class="metaValue"&gt;
              @switch (Model.Type) {
                  case MetaDataDataTypeEnum.NUMBER:
                  case MetaDataDataTypeEnum.DATE:
                  case MetaDataDataTypeEnum.TEXT:
                  case MetaDataDataTypeEnum.TIME:
                  case MetaDataDataTypeEnum.DATETIME:
                      @Html.TextBoxFor(m =&gt; m.Value)
                      break;
                  case MetaDataDataTypeEnum.MULTITEXT:
                      @Html.TextAreaFor(m =&gt; m.Value)
                      break;
                  case MetaDataDataTypeEnum.SELECTION:
                      if(Model.Options != null) {
                          foreach (var o in Model.Options) {
                              @Html.RadioButtonFor(m =&gt; Model.SelectedMetaDataOptionID, o.MetaDataOptionID)
                              @o.Value
                          }
                      }
                      break;
                  case MetaDataDataTypeEnum.MULTISELECTION:
                      @Html.EditorFor(m =&gt; m.Options)
                      break;
              }
          &lt;/div&gt;
      &lt;/div&gt;
  }
  </code></pre>
  
  <p>I added another EditorTemplate for the MULTISELECTION EditorFor:</p>
  
  <pre><code>@model MetaDataOption
  
  @if(Model != null) {
      @Html.HiddenFor(m =&gt; m.MetaDataOptionID)
      @Html.CheckBoxFor(m =&gt; m.Selected)
      @Model.Value
  }
  </code></pre>
  
  <p>And the MetaDataOption class is as follows:</p>
  
  <pre><code>public class MetaDataOption {
      /// &lt;summary&gt;
      /// The unique ID for the option
      /// &lt;/summary&gt;
      public int MetaDataOptionID { get; set; }
  
      /// &lt;summary&gt;
      /// The pre-defined option value to display.
      /// &lt;/summary&gt;
      public string Value { get; set; }
  
      /// &lt;summary&gt;
      /// The sort order of the value in the list.
      /// &lt;/summary&gt;
      public int SortOrder { get; set; }
  }
  </code></pre>
  
  <p>The model now populates correctly when submitted. However, validation occurs on ALL checkboxes rendered by the editor template (in the post action method, as I have not yet implemented the client validation), yet there are no data annotations on the "Selected" member of the MetaDataOption class.</p>
  
  <p>Something to note - the "Options" member of the MetaData class is null if no checkboxes are selected.</p>
  
  <p>I have tried to set the Selected property to a nullable bool (bool?), but I run in to issues with the CheckBoxFor() helper in the MetaDataOption editor template not accepting a nullable bool.</p>
  
  <p>This is sending me crazy... I've spent two days trying to get something working that I'd have whizzed through with ASP.Net!</p>