How to read modelstate errors when returned by Jso

2019-01-16 06:02发布

问题:

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

回答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"
    });
}


回答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); } );


回答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);
}


回答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;
}


回答5:

See below for code with a few amendments to Brent's answer. CheckValidationErrorResponse looks for the Validation Summary regardless of whether it's in the valid or invalid state, and inserts it if not found. If validation errors are found in the response, it applies the validation-summary-errors class to the Summary, else it applies validation-summary-valid. It assumes CSS is present to control the visibility of the Summary.

The code clears existing instances of field-validation-error, and reapplies them for errors found in the response.

function getValidationSummary(form) {
    var $summ = $(form).find('*[data-valmsg-summary="true"]');

    if ($summ.length == 0)
    {
    $summ = $('<div class="validation-summary-valid" data-valmsg-summary="true"><ul></ul></div>');
        $summ.appendTo(form);
    }

    return $summ;
}

function getValidationList(summary) {
    var $list = $(summary).children('ul');

    if ($list.length == 0) {
        $list = $('<ul></ul>');
        $list.appendTo(summary);
    }

    return $list;
}

function getResponseValidationErrors(data) {
    if (data && data.ModelErrors && data.ModelErrors.length > 0)
        return data.ModelErrors;
    return null;
}

function CheckValidationErrorResponse(data, form, summaryElement) {
    var errors = getResponseValidationErrors(data);
    var $summ = summaryElement || getValidationSummary(form);
    var $list = getValidationList($summ);

    $list.html('');

    $(form).find(".field-validation-error")
           .removeClass("field-validation-error")
           .addClass("field-validation-valid");

    if (!errors)
    {
        $summ.removeClass('validation-summary-errors').addClass('validation-summary-valid');
        return false;
    }

    $.each(errors, function (i, item) {
        var $val, $input, errorList = "";
        if (item.Name) {
            $val = $(form).find(".field-validation-valid, .field-validation-error")
                          .filter("[data-valmsg-for=" + item.Name + "]")
                          .removeClass("field-validation-valid")
                          .addClass("field-validation-error");

            $input = $(form).find("*[name='" + item.Name + "']");

            if (!$input.is(":hidden") && !$val.length)
            {
                $input.parent().append("<span class='field-validation-error' data-valmsg-for='" + item.Name + "' data-valmsg-replace='false'>*</span>");
            }

            $input.addClass("input-validation-error");
        }

        $.each(item.Errors, function (c, err) {
            errorList += "<li>" + err + "</li>";
        });

        $list.append(errorList);
    });

    $summ.removeClass('validation-summary-valid').addClass('validation-summary-errors');
    return true;
}


回答6:

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