How to read modelstate errors when returned by Json?

24,385

Solution 1

If you are returning JSON, you cannot use ModelState. Everything that the view needs should be contained inside the JSON string. So instead of adding the error to the ModelState you could add it to the model you are serializing:

public ActionResult Index()
{
    return Json(new 
    {
        errorControl = "_FORM",
        errorMessage = "Username or password is incorrect.",
        someOtherProperty = "some other value"
    });
}

Solution 2

This is draft code but the same idea works for me in production. The main idea here is that Json errors have predefined tag names, that no normal objects will have. For errors validation errors HTML is re-created using JavaScript (both top summary and form elements highlighting).

Server side:

  public static JsonResult JsonValidation(this ModelStateDictionary state)
  {
     return new JsonResult
     {
        Data = new
           {
              Tag = "ValidationError",
              State = from e in state
                      where e.Value.Errors.Count > 0
                      select new
                      {
                         Name = e.Key,
                         Errors = e.Value.Errors.Select(x => x.ErrorMessage)
                            .Concat(e.Value.Errors.Where(x => x.Exception != null).Select(x => x.Exception.Message))
                      }
           }
     };
  }

  in action:
  if (!ModelState.IsValid && Request.IsAjaxRequest())
      return ModelState.JsonValidation();

Client side:

function getValidationSummary() {
   var el = $(".validation-summary-errors");
   if (el.length == 0) {
      $(".title-separator").after("<div><ul class='validation-summary-errors ui-state-error'></ul></div>");
      el = $(".validation-summary-errors");
   }
   return el;
}

function getResponseValidationObject(response) {
   if (response && response.Tag && response.Tag == "ValidationError")
      return response;
   return null;
}

function CheckValidationErrorResponse(response, form, summaryElement) {
   var data = getResponseValidationObject(response);
   if (!data) return;

   var list = summaryElement || getValidationSummary();
   list.html('');
   $.each(data.State, function(i, item) {
      list.append("<li>" + item.Errors.join("</li><li>") + "</li>");
      if (form && item.Name.length > 0)
         $(form).find("*[name='" + item.Name + "']").addClass("ui-state-error");
   });
}

$.ajax(... function(response) { 
   CheckValidationErrorResponse(xhr.responseText); } );

Solution 3

Why not return the original ModelState object to the client, and then use jQuery to read the values. To me it looks much simpler, and uses the common data structure (.net's ModelState)

C#:

return Json(ModelState);

js:

var message = "";
if (e.response.length > 0) {
    $.each(e.response, function(i, fieldItem) {
        $.each(fieldItem.Value.Errors, function(j, errItem) {
            message += errItem.ErrorMessage;
        });
        message += "\n";
    });
    alert(message);
}

Solution 4

this is a tiny tweak to queen3's client side code which handles specific validation messages, and creates a similar document to that created by MVC3:

function getValidationSummary() {
   var $el = $(".validation-summary-errors > ul");
   if ($el.length == 0) {
       $el = $("<div class='validation-summary-errors'><ul></ul></div>")
                .hide()
                .insertBefore('fieldset:first')
                .find('ul');
   } 
   return $el;
}
function getResponseValidationObject(response) {
   if (response && response.Tag && response.Tag == "ValidationError")
      return response;
   return null;
}
function isValidationErrorResponse(response, form, summaryElement) {
    var $list,
        data = getResponseValidationObject(response);
    if (!data) return false;
    $list = summaryElement || getValidationSummary();
    $list.html('');
    $.each(data.State, function (i, item) {
        var $val, lblTxt, errorList ="";
        if (item.Name) {
            $val = $(".field-validation-valid,.field-validation-error")
                        .first("[data-valmsg-for=" + item.Name + "]")
                        .removeClass("field-validation-valid")
                        .addClass("field-validation-error");
            $("input[name=" + item.Name + "]").addClass("input-validation-error")
            lblTxt = $("label[for=" + item.Name + "]").text();
            if (lblTxt) { lblTxt += ": "; }
        }
        if ($val.length) {
            $val.text(item.Errors.shift());
            if (!item.Errors.length) { return; }
        }
        $.each(item.Errors, function (c,val) {
            errorList += "<li>" + lblTxt + val + "</li>";
        });
        $list.append(errorList);
    });
    if ($list.find("li:first").length) {$list.closest("div").show(); }
    return true;
}

Solution 5

C#

 public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }

JavaScript

$.ajax({
        type: "GET",
        url: "/api/xxxxx",
        async: 'false',
        error: function (xhr, status, err) {
            if (xhr.status == 400) {
                DisplayModelStateErrors(xhr.responseJSON.ModelState);
            }
        },
....


function DisplayModelStateErrors(modelState) {
    var message = "";
    var propStrings = Object.keys(modelState);

    $.each(propStrings, function (i, propString) {
        var propErrors = modelState[propString];
        $.each(propErrors, function (j, propError) {
            message += propError;
        });
        message += "\n";
    });

    alert(message);
};
Share:
24,385
learning
Author by

learning

Updated on September 07, 2020

Comments

  • learning
    learning over 3 years

    How can I display ModelState errors returned by JSON?

    I want to do something like this:

     if (!ValidateLogOn(Name, currentPassword))
        {
            ModelState.AddModelError("_FORM", "Username or password is incorrect.");
    
            //Return a json object to the javascript
            return Json(new { ModelState });
        }
    

    What must be my code in the view to read the ModelState errors and display them?

    My actual code in the view to read the JSON values is as follows:

    function createCategoryComplete(e) { 
        var obj = e.get_object(); 
        alert(obj.Values); 
    }