25

I'm developing a web app with asp.net mvc 3 and I have some form that POST to an async action (via ajax). This action recives a ViewModel with some data annotations to validate it. The validation works fine but when the validators return a error I don't know how can I return it to show in my view (because the POST was made by ajax).

My action is something like:

[HttpPost]
public ActionResult SaveCustomer(CustomerViewModel input) {
    if (!ModelState.IsValid) { // <-- business validation
        return Json(new { success = false, errors = ???});
    }
    // persist 
    return Json(new { success = true });
}

How can I show this errors with jquery validate in my view? If it's possible to post some code to sample... I would appretiate that!

Thanks guys!

4 Answers 4

54

Instead of sending JSON in case of error I would put the form inside a partial and then have the controller action return this partial in case of error. The problem with JSON is that you could in fact fetch the errors from the ModelState using LINQ, but it could be a PITA to show them on the view.

So:

<div id="myform">
    @Html.Partial("_MyForm")
</div>

and then inside _MyForm.cshtml:

@model CustomerViewModel
@using (Html.BeginForm())
{
    @Html.EditorFor(x => x.Foo)
    @Html.ValidationMessageFor(x => x.Foo)
    <br />
    @Html.EditorFor(x => x.Bar)
    @Html.ValidationMessageFor(x => x.Bar)
    <br />
    <input type="submit" value="OK" />
}

and the controller action would become:

[HttpPost]
public ActionResult SaveCustomer(CustomerViewModel model)
{
    if (!ModelState.IsValid)
    {
        return PartialView("_MyForm", model);
    }
    return Json(new { success = true });
}

and the last step is to AJAXify this form which could be done in a separate javascript file:

$(function () {
    $('#myform').delegate('form', 'submit', function () {
        $.ajax({
            url: this.action,
            type: this.method,
            data: $(this).serialize(),
            success: function (result) {
                if (result.success) { 
                    // We have a JSON object in case of success
                    alert('success');
                } else {
                    // We have the partial with errors in case of failure
                    // so all we have to do is update the DOM
                    $('#myform').html(result);
                }
            }
        });
        return false;
    });
});
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks again Darin, I'll do this. :)
Just an FYI, it appears that the delegate method has been deprecated in favor of the "on" method in jQuery. "As of jQuery 1.7, .delegate() has been superseded by the .on() method. For earlier versions, however, it remains the most effective means to use event delegation. More information on event binding and delegation is in the .on() method. " - api.jquery.com/delegate
Great answer. It helped me.
Sorry, for the previous comment. Its working great. Earlier I put "content type" as "json" in ajax request, after removing that it works great...thanks.
This is exactly what I needed. Cheers.
|
20

You could return the ModelState errors. This is untested, but something like this should work.

return Json(new
            {
                success = false,
                errors = ModelState.Keys.SelectMany(k => ModelState[k].Errors)
                                .Select(m => m.ErrorMessage).ToArray()
            });

Edit: To display the errors in a view you just check to see if you were successful, and if not, iterate over the list of errors and put them where you show your errors.

if(!result.success) {
    for(var error in result.errors) {
        $('#errorMessages').append(error + '<br />');
    }
}

I prefer Darin's answer for most projects, but your consumer may not always be your own application in which case returning a list of errors is more appropriate since it would allow the consumer to display the errors however they want.

2 Comments

Yes, but, how would you do on View ?
@Felipe I added an example of how to display the errors in the ajax result. I've also simplified the return type to an array of strings for errors.
8

Create a class to represent ModelState Errors for individual properties.

public class ValidationError
{

    public string PropertyName = "";
    public string[] ErrorList = null;
}

Create A Method which returns a List of ValidationErrors based on the ModelState

    public IEnumerable<ValidationError> GetModelStateErrors(ModelStateDictionary modelState)
    {
        var errors = (from m in modelState
                            where m.Value.Errors.Count() > 0
                            select
                               new ValidationError
                               {
                                   PropertyName = m.Key,
                                   ErrorList = (from msg in m.Value.Errors
                                                  select msg.ErrorMessage).ToArray()
                               })
                            .AsEnumerable();
        return errors;
    }

Then in your controller Post Method do:

        if (!ModelState.IsValid)
        {
            return Json(new
            {
                errors = true,
                errorList = GetModelStateErrors(ModelState)
            }, JsonRequestBehavior.AllowGet);
        }

You can create a JS Functions that loops through the error list returned above

$.ajax({
            cache: false,
            async: true,
            type: "POST",
            url: form.attr('action'),
            data: form.serialize(),
            success: function (data) {
                if (data.errors) {
                    displayValidationErrors(data.errorList);
                 }
            },
        error: function (result) {
            console.log("Error");
        }

    });

function displayValidationErrors(errors) {
    $.each(errors, function (idx, validationError) {

        $("span[data-valmsg-for='" + validationError.PropertyName + "']").text(validationError. ErrorList[0]);

    });
}

In the example above I am only getting the first error message from 'ErrorList'. You can create an additional loop to get all the messages and append to your validation span.

Comments

2

Try this code. This is a simple solution for your problem. It will join all the model state errors into one string.

[HttpPost]
    public ActionResult SaveCustomer(CustomerViewModel input) {
        if (!ModelState.IsValid) { // <-- business validation
            return Json(new { success = false, errors = string.Join("; ", ModelState.Values
                                            .SelectMany(x => x.Errors)
                                            .Select(x => x.ErrorMessage));});
        }
        // persist 
        return Json(new { success = true });
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.