ASP.net MVC Como converter erros do ModelState em json

127

Como você obtém uma lista de todas as mensagens de erro do ModelState? Encontrei este código para obter todas as chaves: ( Retornando uma lista de chaves com erros do ModelState )

var errorKeys = (from item in ModelState
        where item.Value.Errors.Any() 
        select item.Key).ToList();

Mas como eu receberia as mensagens de erro como IList ou IQueryable?

Eu poderia ir:

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

Mas isso é feito manualmente - certamente existe uma maneira de fazer isso usando o LINQ? A propriedade .ErrorMessage está tão longe na cadeia que não sei como escrever o LINQ ...

JK.
fonte

Respostas:

192

Você pode colocar qualquer coisa que você quer dentro da selectcláusula:

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

Edição : você pode extrair vários erros em itens de lista separados, adicionando uma fromcláusula, como esta:

var errorList = (from item in ModelState.Values
        from error in item.Errors
        select error.ErrorMessage).ToList();

Ou:

var errorList = ModelState.Values.SelectMany(m => m.Errors)
                                 .Select(e => e.ErrorMessage)
                                 .ToList();

2 nd EDIT : Você está procurando um Dictionary<string, string[]>:

var errorList = ModelState.ToDictionary(
    kvp => kvp.Key,
    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
SLaks
fonte
Isso é uma resposta rápida :)! Ei, isso parece bom, mas e se ModelState [item.Key] tiver mais de um erro? Erros [0] funcionam apenas para uma única mensagem de erro
JK.
Como você deseja combiná-los?
SLaks
Obrigado, é quase isso - mas você seleciona todas as chaves mesmo que não haja erros - como podemos filtrar as chaves sem erros?
JK.
4
Add.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks
3
Para obter a mesma saída como de Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);você deve usar 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());Caso contrário, você não terá os ExceptionMessages
Silvos
74

Aqui está a implementação completa com todas as peças reunidas:

Primeiro, crie um método de extensão:

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

Em seguida, chame esse método de extensão e retorne os erros da ação do controlador (se houver) como json:

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

E, finalmente, mostre esses erros no lado do cliente (no estilo jquery.validation, mas pode ser facilmente alterado para qualquer outro estilo)

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());
    }
}
JK.
fonte
Parece um método interessante, no entanto, a classe auxiliar não está funcionando para mim. Talvez isso ocorra devido a alterações no MVC 2? Estou recebendo um erro que o método ToDictionary não existe no modelState.
Cymen
@ Cyymen você está esquecendo de referenciar System.Linq? ToDictionary () é um método de extensão LINQ.
Nathan Taylor
8
Sujeito às suas preferências .Where(m => m.Value.Count() > 0)também pode ser escrito como .Where(m => m.Value.Any()).
Manfred
Isso pode ser usado de maneira semelhante ao ModelState.ToDataSourceResult () do Kendo.Mvc para retornar erros à Grade e exibir mensagens de erro na edição.
Malnosna
22

Eu gosto de usar Hashtableaqui, para que eu receba o objeto JSON com propriedades como chaves e erros como valor no formulário da matriz de cadeias.

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

Dessa forma, você obtém a seguinte resposta:

{
   "success":false,
   "errors":{
      "Phone":[
         "The Phone field is required."
      ]
   }
}
Jovica Zaric
fonte
8

Existem muitas maneiras diferentes de fazer isso que funcionam. Aqui está agora eu faço isso ...

if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}
Dean North
fonte
2
Você também pode retornar BadRequest(ModelState)e ele será serializado no JSON para você.
Fred
6

A maneira mais fácil de fazer isso é simplesmente retornar a BadRequestcom o ModelState:

Por exemplo, em um PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Se usarmos anotações de dados em, por exemplo, um número de celular, como este, na Updateclasse:

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

Isso retornará o seguinte em uma solicitação inválida:

{
  "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."
    ]
  }
}
Erik A. Brandstadmoen
fonte
1
BadRequest é específico da WebAPI e esta pergunta é sobre MVC.
Rgripper
5

@JK me ajudou muito, mas por que não:

 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;
        }
h45d6f7d4f6f
fonte
3

Dê uma olhada no System.Web.Http.Results.OkNegotiatedContentResult.

Ele converte o que você joga nele em JSON.

Então eu fiz isso

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

return Ok(errorList);

Isso resultou em:

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

Ainda estou para verificar o que acontece quando há mais de um erro para cada campo, mas o ponto é que OkNegoriatedContentResult é brilhante!

Tenho a ideia linq / lambda de @SLaks

ozzy432836
fonte
3

Maneira simples de conseguir isso usando a funcionalidade incorporada

[HttpPost]
public IActionResult Post([FromBody]CreateDoctorInput createDoctorInput) {
    if (!ModelState.IsValid) {
        return BadRequest(ModelState);
    }

    //do something
}

Resultado JSON será

Nisfan
fonte
2

ToDictionary é uma extensão enumerável encontrada no System.Linq empacotado na dll System.Web.Extensions http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Aqui está a aparência da classe completa para mim.

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

    }

}
philrabin
fonte
2

Por que não retornar o ModelStateobjeto original ao cliente e, em seguida, use o jQuery para ler os valores. Para mim, parece muito mais simples e usa a estrutura de dados comum (.net ModelState)

para retornar o ModelStatecomo Json, simplesmente passe-o para o construtor de classe Json (funciona com QUALQUER objeto)

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);
        }
d.popov
fonte
1

Variação com tipo de retorno em vez de retornar IEnumerable

public static class ModelStateHelper
{
    public static IEnumerable<KeyValuePair<string, string[]>> 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;
    }
}
Jeff Circeo
fonte
0

Eu criei e extensão que retorna string com separador "" (você pode usar o seu próprio):

   public static string GetFullErrorMessage(this ModelStateDictionary modelState) {
        var messages = new List<string>();

        foreach (var entry in modelState) {
            foreach (var error in entry.Value.Errors)
                messages.Add(error.ErrorMessage);
        }

        return String.Join(" ", messages);
    }
Niyaz Mukhamedya
fonte
-1
  List<ErrorList> Errors = new List<ErrorList>(); 


        //test errors.
        var modelStateErrors = this.ModelState.Keys.SelectMany(key => this.ModelState[key].Errors);

        foreach (var x in modelStateErrors)
        {
            var errorInfo = new ErrorList()
            {
                ErrorMessage = x.ErrorMessage
            };
            Errors.Add(errorInfo);

        }

se você usar jsonresult, retorne

return Json(Errors);

ou você pode simplesmente devolver o modelStateErrors, eu não tentei. O que fiz foi atribuir a coleção de erros ao meu ViewModel e depois fazer um loop. Nesse caso, posso retornar meus erros via json. Eu tenho uma classe / modelo, eu queria pegar a fonte / chave, mas ainda estou tentando descobrir.

    public class ErrorList
{
    public string ErrorMessage;
}
CyberNinja
fonte