Display mvc partial view with errors on parent pag

2019-03-29 21:45发布

问题:

I have a page with multiple forms, each as a partial. I want to post each partial on submit. If there are errors, I want the validation errors to show in the partial as part of the main page i.e. I don't want to just see the partial on it's own page if there are errors. Am I correct in saying this behavior is only possible with an ajax post? How would I return the model state errors WITHOUT an ajax post, just a normal form post?

Edit: Still seeing the partial on it's own page

Partial -

@using (Html.BeginForm("Login", "Account", FormMethod.Post, new { id = "LoginForm" }))
{
    @Html.ValidationMessage("InvalidUserNamePassword")
    <fieldset class="fieldset">
        <div>
            <label for="form-field-user_id">User ID</label>
            <span>
                @Html.TextBoxFor(x => x.Username, new { @class = "form-field__input form-field__input--text", @id = "form-field-user_id"})                      
            </span>
        </div>
    </fieldset>
    <div class="form-field__button">
        <button id="loginButton" type="submit" class="button button--primary">Login</button>
    </div>
}

<script>
    $('#loginButton').click(function () {
        $.ajax({
            type: "POST",
            url: '@Url.Action("Login", "Account")',
            data: $('form').serialize(),
            success: function (result) {
                if (result.redirectTo) {
                    window.location.href = result.redirectTo;
                } else {
                    $("#LoginForm").html(result);
                }
            },
            error: function () {
                $("#LoginForm").html(result);
            }
        });
    });
</script>

Controller -

[HttpPost]
public ActionResult Login(LoginModel model)
{
    if (!ModelState.IsValid)
    {
        return PartialView("~/Views/Account/_Login.cshtml", model);
    }
    return Json(new { redirectTo = Url.Action("Index", "Profile") });
}

回答1:

Yes, you are correct in saying this behavior is only possible with an ajax post.

There are a few problems with your current script meaning that you will not get the desired results.

Firstly your button is a submit button meaning that it will do a normal submit in addition to the ajax call unless you cancel the default event (by adding return false; as the last line of code in your script). However it would be easier to just change the button type to type="button"

<button id="loginButton" type="button" class="button button--primary">Login</button>

The ajax call will now update the existing page, however it will add the returned partial inside the existing <form> element resulting in nested forms which is invalid html and not supported. Change your html to wrap the main views form in another element

<div id="LoginFormContainer">
    @using (Html.BeginForm("Login", "Account", FormMethod.Post, new { id = "LoginForm" }))
    {
        ....
        <button id="loginButton" type="button" class="button button--primary">Login</button>
    }
</div>

and then modify the script to update the html of the outer element

success: function (result) {
    if (result.redirectTo) {
        window.location.href = result.redirectTo;
    } else {
        $("#LoginFormContainer").html(result); // modify
    }
},

Finally, your rendering dynamic content so client side validation will not work for the returned form. Assuming your properties have validation attributes (for example the [Required] attribute on the Userame property), you need to reparse the validator after loading the content

var form = $('#LoginForm');
....
} else {
    $("#LoginFormContainer").html(result);
    // reparse validator
    form.data('validator', null);
    $.validator.unobtrusive.parse(form);
}

You noted that you have multiple forms on the page, in which case your ajax options should be

data: $('#LoginForm').serialize(),

or if your declare var form = $('#LoginForm'); as per the above snippet, then data: form.serialize(), to ensure you are serializing the correct form.

Side note: There is no real need to change the id attribute of the textbox (it will be id=Username" by default and you can simply use

@Html.LabelFor(x => x.UserName, "User ID")
@Html.TextBoxFor(x => x.Username, new { @class = "form-field__input form-field__input--text" })  

or just @Html.LabelFor(x => x.UserName) of the property is decorated with [Display(Name = "User ID")]