Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC 3 Razor @Html.ValidationMessageFor not working in partial loaded via jquery.load()

I have put together a small example here just to replicate the problem. I have a strongly typed partial view _Name.cshtml:

@model ValidationInPartial.ViewModels.MyViewModel

<h2>@ViewBag.Message</h2>

    <fieldset>
        <legend>Name</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.MyName)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.MyName)
            @Html.ValidationMessageFor(model => model.MyName)
        </div>

        <a href="#" id="reload">Reload Name</a>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>

<script type="text/javascript">
    $(document).ready(function () {
        $("#reload").click(function () {
            $("#divName").load("Home/NameReload");
        });
    });
</script>

that is initially loaded and displayed inside the main Index.cshtml

<div id="divForm">
    @using (Html.BeginForm()) {

        <div id="divName">
            @Html.Partial("_Name")
        </div>
    }
</div>

The field MyName is required and validation is implemented through Required attribute in MyViewModel

namespace ValidationInPartial.ViewModels
{
    public class MyViewModel
    {
        [Required(ErrorMessage = "Please enter a Name.")]
        public string MyName { get; set; }
    }
}

After the page is loaded the first time, if you click the Create button leaving the field empty the validation message "Please enter a Name." shows beside the field and the field itself turns pink, which is the expected behaviour. Now by clicking the "Reload Name" link, which makes an ajax call (jquery.load(...)), the partial is reloaded, here is controller code:

public PartialViewResult NameReload()
{
    MyViewModel myViewModel = new MyViewModel();
    ViewBag.Message = "Name Reloaded";
    return PartialView("_Name", myViewModel);
}

This time if you click the Create button leaving the field empty the validation message does not appear beside the field, although the field turns pink. It turns out that when reloading the partial the @Html.ValidationMessageFor doesn't render the validation message as the first time.

Here is the jquery files I use

<script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>

I wonder if this is a bug in the way the Razor engine renders the @Html.ValidationMessageFor or is that a problem with jquery? Any idea why this happens?

I have also read somewhere that the ajax call looses all the scripts for the page, in fact I have to keep any javascript code inside the partial so that they can be rendered and used again.

In the meantime I found a workaround which is to manually render in the partial what was supposed to be rendered by @Html.ValidationMessageFor which is:

<span class="field-validation-valid" data-valmsg-replace="true" data-valmsg-for="MyName"></span>

However this workaround means that if we change the type of validation or just the validation message inside the Required attribute in the ViewModel, we need to modify this hard-coded piece of html in the view.

like image 732
Roberto D Avatar asked Jun 24 '11 09:06

Roberto D


3 Answers

@NickBork has a great answer here. The key is that ASP.NET's MVC rendering engine does not output the validation script if it doesn't think that there is a form. The example given hacks it buy putting in a form and then selection an inner section of HTML from was was returned, essentially throwing the outer wrapper of the form away.

There is another method so that you can just get your view:

ViewContext.FormContext = new FormContext();

With this method, there won't actually be FORM code output, but the validation markup will be there.

Thanks, John

like image 90
John Fager Avatar answered Oct 19 '22 20:10

John Fager


Validation markup (span tags, custom field attributes, etc) are not rendered unless your fields are contained within a FORM. The validation plugin itself does not work with elements outside of a form.

When ASP.NET renders your Partial View the controls are not within a form and thus do not get the elements rendered.

When you load you're partial content you'll need to parse the HTML using a jQuery selector.

In my sample below I have a TBODY on the parent View page that contains rows. When I need to add additional rows, I make a call to a View which had a form, table, tbody and collection of rows.

$.ajax({
    type: "POST",
    url: "/controller/action",
    data: ({Your: 'dataHere'}),
    dataType: "html",
    success:
        function(response){
            $('tbody').append($('tbody',$(response)).html());

            //The validation plugin can't bind to the same form twice.
            //We need to remove existing validators
            $('form').removeData("validator");

            //Refresh the validators
            $.validator.unobtrusive.parse(document);
        },
    error:
        function(){
            alert('An error occured while attempting to add the new content');
        }
});

Note that I'm using a jQuery selector to select the rows that are inside of the View/PartialView that are loaded in by using AJAX:

$('tbody',$(response)).html()

The rest of the wrapper just appends the rows from the AJAX View/PartialView to the calling parents tbody:

$('tbody').append($('tbody',$(response)).html());

A couple other notes, after the validator plugin has been run on a form, it can not be called again without re-adding it (see jquery.validate.unobtrusive not working with dynamic injected elements)

To fix this, I first call the following method to remove all validators:

$('form').removeData("validator");
$("form").removeData("unobtrusiveValidation");

I then refresh the validators using the following:

$.validator.unobtrusive.parse(document);
like image 18
Nick Bork Avatar answered Oct 19 '22 20:10

Nick Bork


I can't remember where I found the solution. The reason is because you are loading a PartialView into a View that has already been parsed by the jquery.validator.unobtrusive library. You need to re-parse the unobtrusive library

    function ReparseValidation(){
       jQuery.validator.unobtrusive.parse("#yourcontainer");
    }
like image 2
Vincent Avatar answered Oct 19 '22 22:10

Vincent