Search code examples
validationasp.net-mvc-5asp.net-mvc-viewmodel

Validation always fired for fields dynamically added with partial view


Using a main view and partial view to add fields dynamically to it with ModelViews. The issue is the ModelState.isValid always returns falls because there are 2 fields in the partial view which are failing validation. These fields are not intended to be mandatory so I have made it nullable but still these are getting validated. How can overcome this situation?

Code: View Main :

@model ViewModel.EnquiryVM

@using (Html.BeginForm("Create", "Enquiries", FormMethod.Post))
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">

        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.EnquiryNumber, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-3">
                @Html.EditorFor(model => model.EnquiryNumber, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.EnquiryNumber, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.ClientID, "Client", htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-3">

                @Html.DropDownListFor(u => u.ClientID, (IEnumerable<SelectListItem>)Model.Clients, "--Select--")
                @Html.ValidationMessageFor(model => model.ClientID, "", new { @class = "text-danger" })
            </div>
        </div>

         <div id="LineItems">
               <div id="editorRowsLineitems">
                    @foreach (var item in Model.LineItems)
                    {
                        @Html.Partial("_CreateEnquiryItem", item)
                    }
                </div>
                @Html.ActionLink("Add Items", "CreateLineItem", null, new { id = "addItem", @class = "button" });

        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
<script type="text/javascript">
    $(function () {
        $('#addItem').on('click', function () {
            $.ajax({
                url: '@Url.Action("CreateLineItem")',
                    cache: false,
                    success: function (html) {
                        $("#editorRowsLineitems").append(html);

                        $('form').data('validator', null);
                        $.validator.unobtrusive.parse($('form'));
                    }
                });
                return false;
            });
        $('#editorRowsLineitems').on('click', '.deleteRow', function () {
                $(this).closest('.editorRow').remove();
            });
        $('form').data('validator', null);
        $.validator.unobtrusive.parse($('form'));
    });


</script>
}

Partial View :

@model ViewModels.EnquiryLineItemVM

<div class="editorRow">
    @using (Html.BeginCollectionItem("LineItems"))
    {
        <table class="table">

            <tr>
                <td>
                    @Html.EditorFor(model => model.ItemDesc)

                </td>
                <td>
                    @Html.EditorFor(model => model.Quantity)

                </td>

                <td>
                    @Html.DropDownListFor(model => model.ManufacturerId, Model.ManufacturerList, "--Please Select--")

                </td>
                <td>

                    <a href="#" class="deleteRow">Delete</a>
                </td>
            </tr>
        </table>

    }

ViewModels :

public class EnquiryVM
    {
        public int ID { get; set; }

        [Required]
        public string EnquiryNumber { get; set; }
        public int ClientID { get; set; }
        public IEnumerable<SelectListItem> Clients { get; set; }
        public int ItemID { get; set; }
        public List<EnquiryLineItem> LineItems { get; set; }

    }
  public class EnquiryLineItemVM
    {
        public int? EnquiryID { get; set; } //nullable but still validation fires
        [Required]
        public string ItemDesc { get; set; }
        public int Quantity { get; set; }
        public int? ManufacturerId { get; set; } // this too fires
        public IEnumerable<SelectListItem> ManufacturerList { get; set; }
    }

Controller Action Method

 [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create( EnquiryVM enquiryVM)
        {
            var errors = ModelState.Keys.Where(k => ModelState[k].Errors.Count > 0).Select(k => new { propertyName = k, errorMessage = ModelState[k].Errors[0].ErrorMessage });// for error debugging shows Validation errors for EnquiryID and ManufacturerID 
            var enquiry = new Enquiry();
            enquiry.EnquiryNumber = enquiryVM.EnquiryNumber;
            enquiry.ClosingDate = enquiryVM.ClosingDate;
            enquiry.RFQSentDate = enquiryVM.RFQSentDate;
            enquiry.ClientID = enquiryVM.ClientID;
            enquiry.DivisionID = enquiryVM.DivisionID;
            enquiry.EnquiryLineItems = enquiryVM.LineItems;

            if (ModelState.IsValid)
            {
                db.Enquiries.Add(enquiry);
                enquiryVM.ID = enquiry.ID;
                foreach (var item in enquiry.EnquiryLineItems)
                {
                    if (enquiryVM.ID != null)
                    {
                        item.EnquiryID = (int)enquiryVM.ID;
                    }
                    db.EnquiryLineItems.Add(item);
                }

                db.SaveChanges();
                return RedirectToAction("Index");
            }

            var viewModel = GetAllCategories();
            return View(viewModel);
        }

Unable to fix this. Thanks for your time.

ManufacturerID and EnquiryID is the FK relationship used in Model , but I'm using ViewModels which is nullable for these fields.

enter image description here


Solution

  • The LineItemspropery of EnquiryVM is typeof List<EnquiryLineItem> but the model you have shown for the collection is EnquiryLineItemVM, not EnquiryLineItem.

    I assume EnquiryLineItem is you data model and based on the ModelState errors in the image, both EnquiryId and ManufacturerId are not marked as nullable in that data model.

    Change your EnquiryVM to use your EnquiryLineItemVM view model

    public class EnquiryVM
    {
        public int ID { get; set; }
        [Required]
        public string EnquiryNumber { get; set; }
        ....
        public List<EnquiryLineItemVM> LineItems { get; set; } // change
    
    }
    public class EnquiryLineItemVM
    {
        ....
    }