ASP.NET MVC如何将ModelState错误转换为json

137

如何获取所有ModelState错误消息的列表?我发现了以下代码以获取所有键:

(Returning a list of keys with ModelState errors)
var errorKeys = (from item in ModelState
        where item.Value.Errors.Any() 
        select item.Key).ToList();

但是我该如何将错误消息作为 IList 或 IQueryable 获取?

我可以这样做:

foreach (var key in errorKeys)
{
    string msg = ModelState[error].Errors[0].ErrorMessage;
    errorList.Add(msg);
}

但这样做是手动的,肯定有一种使用LINQ的方法来实现。.ErrorMessage属性在链中是如此之深,以至于我不知道如何编写LINQ...

15个回答

206

你可以在select子句中放置任何你想要的东西:

var errorList = (from item in ModelState
        where item.Value.Errors.Any() 
        select item.Value.Errors[0].ErrorMessage).ToList();

编辑:您可以通过添加from子句将多个错误提取到单独的列表项中,如下所示:

var errorList = (from item in ModelState.Values
        from error in item.Errors
        select error.ErrorMessage).ToList();
或者:
var errorList = ModelState.Values.SelectMany(m => m.Errors)
                                 .Select(e => e.ErrorMessage)
                                 .ToList();

第二次编辑:你正在寻找一个 Dictionary<string, string[]>

var errorList = ModelState.ToDictionary(
    kvp => kvp.Key,
    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);

回复很快!嘿,看起来不错,但是如果ModelState[item.Key]有多个错误怎么办?Errors[0]只适用于单个错误消息。 - JK.
你想如何将它们组合? - SLaks
谢谢,那差不多了 - 但它选择了每个键,即使它没有错误 - 我们如何过滤掉没有错误的键? - JK.
4
添加 .Where(kvp => kvp.Value.Errors.Count > 0) - SLaks
在你的代码中,当需要判断是否存在元素时,应该使用 Any() 方法而不是 Count>0。 .Where(kvp => kvp.Value.Errors.Any()) - Bjego
3
要获得与“Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);”相同的输出,您应该使用以下代码:var errorList = modelState.Where(elem => elem.Value.Errors.Any()) .ToDictionary( kvp => kvp.Key, kvp => kvp.Value.Errors.Select(e => string.IsNullOrEmpty(e.ErrorMessage) ? e.Exception.Message : e.ErrorMessage).ToArray());否则,您将无法获得异常消息。 - Silvos

77

以下是完整的代码实现:

首先创建一个扩展方法:

public static class ModelStateHelper
{
    public static IEnumerable Errors(this ModelStateDictionary modelState)
    {
        if (!modelState.IsValid)
        {
            return modelState.ToDictionary(kvp => kvp.Key,
                kvp => kvp.Value.Errors
                                .Select(e => e.ErrorMessage).ToArray())
                                .Where(m => m.Value.Any());
        }
        return null;
    }
}

然后调用该扩展方法,并将控制器操作返回的错误(如果有)作为 JSON 返回:

if (!ModelState.IsValid)
{
    return Json(new { Errors = ModelState.Errors() }, JsonRequestBehavior.AllowGet);
}

最后,在客户端上显示这些错误(采用jquery.validation样式,但可以很容易地更改为任何其他样式)

function DisplayErrors(errors) {
    for (var i = 0; i < errors.length; i++) {
        $("<label for='" + errors[i].Key + "' class='error'></label>")
        .html(errors[i].Value[0]).appendTo($("input#" + errors[i].Key).parent());
    }
}

这看起来是一个有趣的方法,但是帮助类对我不起作用。这可能是由于MVC 2的更改导致的吗?我收到了一个错误,即modelState上不存在ToDictionary方法。 - Cymen
@Cymen 你是不是忘了引用 System.Linq?ToDictionary() 是一个 LINQ 扩展方法。 - Nathan Taylor
8
根据您的喜好,.Where(m => m.Value.Count() > 0)也可以写成.Where(m => m.Value.Any()) - Manfred
这可以类似于Kendo.Mvc中的ModelState.ToDataSourceResult()一样使用,将错误返回给Grid并在编辑时显示错误消息。 - malnosna

23

我喜欢在这里使用Hashtable,这样我就可以得到以属性作为键和以字符串数组形式表示的错误作为值的JSON对象。

var errors = new Hashtable();
foreach (var pair in ModelState)
{
    if (pair.Value.Errors.Count > 0)
    {
        errors[pair.Key] = pair.Value.Errors.Select(error => error.ErrorMessage).ToList();
    }
}
return Json(new { success = false, errors });

这样,您将获得以下响应:


需要翻译的内容已经被翻译。
{
   "success":false,
   "errors":{
      "Phone":[
         "The Phone field is required."
      ]
   }
}

11

最简单的方法是返回具有ModelState本身的BadRequest

例如,在PUT中:

[HttpPut]
public async Task<IHttpActionResult> UpdateAsync(Update update)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

如果我们在Update类中使用数据注释来标记手机号码,就像这样:

public class Update {
    [StringLength(22, MinimumLength = 8)]
    [RegularExpression(@"^\d{8}$|^00\d{6,20}$|^\+\d{6,20}$")]
    public string MobileNumber { get; set; }
}

在无效请求时,这将返回以下内容:

{
  "Message": "The request is invalid.",
  "ModelState": {
    "update.MobileNumber": [
      "The field MobileNumber must match the regular expression '^\\d{8}$|^00\\d{6,20}$|^\\+\\d{6,20}$'.",
      "The field MobileNumber must be a string with a minimum length of 8 and a maximum length of 22."
    ]
  }
}

2
BadRequest 是 WebAPI 特定的,而这个问题是关于 MVC 的。 - rgripper
3
给新观众的一个提示:现在BadRequest已经可以在.NET Core及以上版本中使用了。 - Erik A. Brandstadmoen

8
有很多不同的方法可以实现这一点,它们都是有效的。以下是我实现的方法...
if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}

5
你还可以返回 BadRequest(ModelState),它将会自动将其序列化为 JSON 格式。 - Fred

5
简单的方法是使用内置功能来实现。
[HttpPost]
public IActionResult Post([FromBody]CreateDoctorInput createDoctorInput) {
    if (!ModelState.IsValid) {
        return BadRequest(ModelState);
    }

    //do something
}

JSON结果如下:


这与2年多前的此答案有何不同? - user692942

5

@JK对我帮助很大,但为什么不使用:

 public class ErrorDetail {

        public string fieldName = "";
        public string[] messageList = null;
 }

        if (!modelState.IsValid)
        {
            var errorListAux = (from m in modelState 
                     where m.Value.Errors.Count() > 0 
                     select
                        new ErrorDetail
                        { 
                                fieldName = m.Key, 
                                errorList = (from msg in m.Value.Errors 
                                             select msg.ErrorMessage).ToArray() 
                        })
                     .AsEnumerable()
                     .ToDictionary(v => v.fieldName, v => v);
            return errorListAux;
        }

3

看一下System.Web.Http.Results.OkNegotiatedContentResult。

它可以将任何你传递给它的内容转换成JSON格式。

因此,我做了这个:

var errorList = ModelState.ToDictionary(kvp => kvp.Key.Replace("model.", ""), kvp => kvp.Value.Errors[0].ErrorMessage);

return Ok(errorList);

这导致了:
{
  "Email":"The Email field is not a valid e-mail address."
}

我还没有检查当每个字段有多个错误时会发生什么,但问题在于OkNegoriatedContentResult非常棒!

从@SLaks那里得到了linq/lambda的想法。


2

ToDictionary是System.Linq中的Enumerable扩展,在System.Web.Extensions dll中找到http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx。以下是我完整的类。

using System.Collections;
using System.Web.Mvc;
using System.Linq;

namespace MyNamespace
{
    public static class ModelStateExtensions
    {
        public static IEnumerable Errors(this ModelStateDictionary modelState)
        {
            if (!modelState.IsValid)
            {
                return modelState.ToDictionary(kvp => kvp.Key,
                    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()).Where(m => m.Value.Count() > 0);
            }
            return null;
        }

    }

}

2
为什么不将原始的“ModelState”对象返回给客户端,然后使用jQuery读取值。在我看来,这样显得更加简单,并且使用了常见的数据结构(.net的“ModelState”)。
要将“ModelState”作为Json返回,只需将其传递给Json类构造函数(适用于任何对象)。
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);
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接