Search code examples
asp.net-mvcvalidationasp.net-mvc-5unobtrusive-validation

asp.net mvc5 validating list of objects


I have a view for entering delivery quantities on every items in the list, and sends a list of objects back to controller. I would like to validate all textboxes on the page, and I have added annotations in the model.

The problem is, I can only validate the first row (also in the output html only the first row has validation markups). Since there are generated validations on the first row, so I don't think it's about the model. Is there a way to have generated validations in all rows? If not, what are the workarounds?

    @{int i = 0;}
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.HiddenFor(modelItem => item.eo, new { Name = "[" + i + "].eo" })
                @Html.DisplayFor(modelItem => item.barcode)
                @Html.Hidden("[" + i + "].barcode", Model[i].barcode)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.itemno)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.cdesc)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.acost)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.qty)
            </td>
            <td>
                @Html.EditorFor(modelItem => item.dqty, new { htmlAttributes = new { Name = "[" + i + "].dqty", id = "[" + i + "].dqty", @class = "form-control" } })
                @Html.ValidationMessage("[" + i + "].dqty", "", new { @class = "text-danger" })
            </td>
        </tr>
        i++;
    }

This is the generated html for the textbox in the first row.

<input Name="[0].dqty" class="form-control text-box single-line" data-val="true" data-val-number="The field 出貨數量 must be a number." data-val-required="必須填上出貨數量" id="[0].dqty" name="item.dqty" type="text" value="10" />
<span class="field-validation-valid text-danger" data-valmsg-for="[0].dqty" data-valmsg-replace="true"></span>

And the second row onwards...

<input Name="[1].dqty" class="form-control text-box single-line" id="[1].dqty" name="item.dqty" type="text" value="7" />
<span class="field-validation-valid text-danger" data-valmsg-for="[1].dqty" data-valmsg-replace="true"></span>

The Model

[MetadataType(typeof(EorderDetailsMetaData))]
public partial class EorderDetails
{
    public string eo { get; set; }
    public string barcode { get; set; }
    public string itemno { get; set; }
    public string cdesc { get; set; }
    public Nullable<decimal> qty { get; set; }
    public Nullable<decimal> dqty { get; set; }
    public Nullable<decimal> acost { get; set; }
    public string sdate { get; set; }
    public string edate { get; set; }
    public string shop { get; set; }
    public string sname { get; set; }
    public string saddr { get; set; }
    public string shoptel { get; set; }
    public string shopfax { get; set; }
}

public class EorderDetailsMetaData
{
    [Display(Name = "訂單編號")]
    public string eo { get; set; }
    [Display(Name = "電腦條碼")]
    public string barcode { get; set; }
    [Display(Name = "貨品編號")]
    public string itemno { get; set; }
    [Display(Name = "貨品名稱")]
    public string cdesc { get; set; }
    [Display(Name = "訂購數量")]
    [DisplayFormat(DataFormatString = "{0:n0}", ApplyFormatInEditMode = true)]
    public Nullable<decimal> qty { get; set; }
    [Display(Name = "出貨數量")]
    [DisplayFormat(DataFormatString = "{0:n0}", ApplyFormatInEditMode = true)]
    [Required(ErrorMessage = "必須填上出貨數量")]
    public Nullable<decimal> dqty { get; set; }
    [Display(Name = "成本價")]
    [DisplayFormat(DataFormatString = "{0:0.##}", ApplyFormatInEditMode = true)]
    public Nullable<decimal> acost { get; set; }
    public string sdate { get; set; }
    public string edate { get; set; }
    public string shop { get; set; }
    public string sname { get; set; }
    public string saddr { get; set; }
    public string shoptel { get; set; }
    public string shopfax { get; set; }

}

Solution

  • You should be generating the collection in a for loop and let the helpers generate the correct html for you. If you inspect the html you have posted in the second snippet you will see the issue (two name attributes!)

    @model IList<EorderDetails>
    @using(Html.BeginForm())
    {
      for(int i = 0; i < Model.Count; i++)
      {
        @Html.HiddenFor(m => m[i].eo)
        @Html.DisplayFor(m => m[i].barcode)
        ....
        @Html.EditorFor(m => m[i].dqty, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(m => m[i].dqty, new { @class = "text-danger" })
      }
      <input type="submit" />
    }
    

    Alternatively you can create a custom EditorTemplate for your model

    /Views/Shared/EditorTemplates/EorderDetails.cshtml

    @model EorderDetails
    @Html.HiddenFor(m => m.eo)
    @Html.DisplayFor(m => m.barcode)
    ....
    

    and in the main view

    @model IList<EorderDetails>
    @using(Html.BeginForm())
    {
      @Html.EditorFor(m => m)
      <input type="submit" />
    }