MVC3 Unobtrusive Validation Not Working after Ajax

2019-01-12 19:04发布

问题:

Ok, here is the deal, I have seen a few posts on SO relating to this issue, but nothing is working for me.

Basically, I have select drop downs that are being loaded from partial views, I am trying to filter contents of each subsequent drop down, based on the previously selected drop down.

If I just put the call to the partial view in the div containers, and load the page, the validation from data annotations works fine, primarily Required attribute.

However, if I try to load the same partial via AJAX as it is setup here, the Required validation does not work, anyone can post the form after that and KABOOM.

I have found people saying that in the Success callback you need to have the client side validator reparse the form, and I am trying that, but it doesn't seem to be working.

I have a view which looks like this...

  @model Area51.Models.Workflow.AddReportableItemToBatchActionModel
@{
    ViewBag.Title = "Add Reportable Item to Batch";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<script type="text/javascript">

    $(function () {
        var fadeDelay = 150;

        $(".jqDatePicker").datepicker({
            dateFormat: 'm/d/yy',
            onSelect: function (date) {
                $("#categoryContainer").show(fadeDelay);
            }
        });

        $('#Category').change(function () {
            RetrieveItemsForCategory();
            $("#itemContainer").show(100);
        });

        $('#Item').live('change', function () {
            RenderPartialForUOMByItem();           
        });



        function RetrieveItemsForCategory() {

            var category = $("#Category :selected").val();

            $.ajax({
                type: "POST",

                url: '@Url.Action("RenderPartialForLocationItemsByCategory","BatchWorkflow")',

                data: 'category=' + category,

                success: function (result) {
                    $("#itemContainer").html(result.toString());
                    $("#itemContainer").show(100);
                    RebindValidation();
                },

                error: function (req, status, error) {
                    alert("Sorry! Could not request items for your selection at this time.");
                }

            });


        }


        function RenderPartialForUOMByItem() {

            var item = $("#Item :selected").val();

            $.ajax({
                type: "POST",

                url: '@Url.Action("RenderPartialForUOMByItem","BatchWorkflow")',

                data: "item=" + item,

                success: function (result) {
                    $("#quantityContainer").html(result.toString());
                    $("#quantityContainer").show(100);
                    RebindValidation();
                },

                error: function (req, status, error) {
                    alert("Sorry! Could not request items for your selection at this time.");
                }

            });
        }

        function RebindValidation() {
            alert("Rebinding Validation");
            $.validator.unobtrusive.parse("#frmAddItem");
        }

    });      // End OnLoad Event
</script>

<h3 class="pageHeader">Batch : @Model.BatchName</h3>

<div align="center">

@{Html.BeginForm("AddItemToBatch", "BatchWorkflow", null, FormMethod.Post, new { id = "frmAddItem" });}

    @Html.ValidationSummary(true)

    <fieldset style="width:60%">
        <legend>Add an Item to the Batch</legend>     

     <div>       
          <h3>Select Date Item was Added</h3>
          @Html.EditorFor(x => x.EventDate,null)
          <br />
      </div>

      <div id="categoryContainer" style="display:none"> 
        <hr />
          <h3>Select an Inventory Category</h3>
          @Html.EditorFor(x => x.Category,null)
          <br />
      </div>

      <div id="itemContainer" style="display:none"> 
        @*   @{Html.RenderAction("RenderPartialForLocationItemsByCategory", "BatchWorkflow", new { category = Model.Category });}*@
      </div>


      <div id="quantityContainer" style="display:none"> 
        @*  @{Html.RenderAction("RenderPartialForUOMByItem", "BatchWorkflow", new { item = Model.Item });}*@
      </div>

      <div id="reportingDataContainer" style="display:none"> 
        <hr />
          <h3>What quantity of the batch was affected by this addition?</h3>
          @Html.EditorFor(x => x.ConsumedWineQuantity) (Gallons)
        <br />
        <hr />
          <h3>What was the increase in Batch Volume as a result of this addition?</h3>
          @Html.EditorFor(x => x.ProducedWineQuantity) (Gallons)
      </div>

        <div style="display:block">
        <div></div>        
            <span><button type="button" id="btnCancel" class="linkButton" value="Cancel" onclick="location.href='@Url.Action("Home","Home",null)';">Cancel</button></span>  
            <span><button type="submit" id="btnSubmit" class="linkButton" value="Add">Add Item</button></span>
        </div>


    </fieldset>
        @{ Html.EndForm(); }
</div>

The Partial Views are very simple, they basically look like this...

@model Area51.Models.Workflow.AddReportableItemToBatchActionModel

      <hr />
          <h3>Select the Item to Add</h3>
          @Html.EditorFor(x => x.Item)
          <br />

Again, if I just RenderPartial, the validation works fine, however when I try to do it via ajax, the validation goes away. The "Rebinding Validation" alert fires, but the $.validator.unobtrusive.parse("#frmAddItem"); doesn't seem to be doing a thing.

Can anyone help with what I am missing? It would be greatly appreciated.

<======================= UPDATE 1 =============================>

OK, I tried adding the $.validator.unobtrusive.parse("#frmAddItem"); at the bottom of the the partial view in a document ready event and it didn't seem to work either, basically nothing changed, I could still submit the form.

I did find a post here : http://xhalent.wordpress.com/2011/01/24/applying-unobtrusive-validation-to-dynamic-content/ that mentioned that when the MVC version of the jqvalidation sees a form already has validation rules bound to it, it just ignores the .validator call. I implemented the script extension that this gentleman used, and the validation is now rebinding to the form using the new extension. I can test this by appending html to the form and calling the new extension, and it is rebinding to the new text box.

However, this still has not completely fixed the issue. I used Firebug to check out the actual content on the fields coming back from the ajax call, and noticed something very strange.

When I use the RenderPartial to call the action, it writes out the following select :

<select id="Item" name="Item" data-val-required="The Item field is required." data-val-number="The field Item must be a number." data-val="true">

However, when I make the ajax call to the same exact controller action, it gives me this back :

<select id="Item" name="Item">

I tried adding the script tags to the partial view as well, but it didn't fix the issue. Is there some reason why the ajax call would be stripping the unobtrusive validation tags?

<======================= UPDATE 2 =============================>

Ok, so what was happening, is I had an editor template for the drop down that took a select list and converted it to an html select. I found a post that mentioned that in order to get data validation attributes to write out on an editor template, you have to have a form context. Since the Html.RenderPartial was being done within a form, then the editor template had a form context to work with. When I was just trying to call the partial via ajax, there was no form context to work with, and instead of complaining it just didn't write out the data validation attributes. Adding a new Form Context in the editor template for the SelectListDropDown fixed the issue.

@{ // fix to stop stupid crappy brad wilson mvc3 code from stripping the jq data valdiation attributes
    if (ViewContext.FormContext == null)
    {
        ViewContext.FormContext = new FormContext();
    }
}

回答1:

$.validator.unobtrusive.parse("#frmAddItem"); will work. Do note that it must be in the partial that you load through ajax (below the form in the partial)

<form id="frmAddItem" method="POST" action="...">
    <!-- all the items -->
</form>
<script type="text/javascript">
    $.validator.unobtrusive.parse("#frmAddItem");
</script>


回答2:

I'm adding my experience as the above recommendations did not work for me. This solution did and may help others that get directed to this page from a search engine:

Add OnSuccess="$.validator.unobtrusive.parse('YourFormName');" to you AjaxOptions

An example using Ajax.ActionLink:

@Ajax.ActionLink("This is a test to get unobtrusive javascript working",
                 "Name_of_your_controller_action",
                 new AjaxOptions { HttpMethod = "POST", 
                                   InsertionMode = InsertionMode.Replace, 
                                   UpdateTargetId = "UserDiv", 
                                   OnSuccess="$.validator.unobtrusive.parse('UserDetailsForm');"  
                                 }
                )

This solution was found at: http://blog.janjonas.net/2011-07-24/asp_net-mvc_3-ajax-form-jquery-validate-supporting-unobtrusive-client-side-validation-and-server-side-validation



回答3:

I wrote this little snippet that will you can place in your javascript file and it will handle all your forms that are ajax loaded.

//enable unobtrusive validation for ajax loaded forms
$(document).ajaxSuccess(function (event, xhr, settings) {
    //process only if html was returned
    if ($.inArray('html', settings.dataTypes) >= 0) {
        //will parse the element with given id for unobtrusive validation
        function parseUnobtrusive(elementId) {
            if (elementId) {
                $.validator.unobtrusive.parse('#' + elementId);
            }
        }

        //get the form objects that were loaded.  Search within divs
        //in case the form is the root element in the string
        var forms = $('form', '<div>' + xhr.responseText + '</div>');

        //process each form retrieved by the ajax call
        $(forms).each(function () {
            //get the form id and trigger the parsing.
            //timout necessary for first time form loads to settle in
            var formId = this.id;
            setTimeout(function () { parseUnobtrusive(formId); }, 100);
        });
    }
});


回答4:

Another option, rather trick, which worked for me. Just add following line in the beginning of the partial view which is being returned by ajax call

this.ViewContext.FormContext = new FormContext(); 

Reference



回答5:

I could only get it the validation to work inside of OnComplete instead of OnSuccess:

Here's the AJAX Code:

@using (Ajax.BeginForm("Index", null, 
                       new AjaxOptions { OnSuccess = "onSuccess", 
                                         OnComplete = "onComplete"}, 
                       new { id = "mainForm" }))

And here's my script:

function onComplete(result) {
    $.validator.unobtrusive.parse("#mainForm");
    alert("Complete");
};