David C David C - 29 days ago 13
C# Question

MVC3 Unobtrusive Validation Not Working after Ajax Call

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();
}
}

Answer

$.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>
Comments