ASP.NET MVC Cómo convertir errores ModelState a json


127

¿Cómo se obtiene una lista de todos los mensajes de error de ModelState? Encontré este código para obtener todas las claves: ( Devolver una lista de claves con errores de ModelState )

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

Pero, ¿cómo obtendría los mensajes de error como IList o IQueryable?

Podría ir:

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

Pero eso es hacerlo manualmente, ¿seguramente hay una manera de hacerlo usando LINQ? La propiedad .ErrorMessage está tan abajo en la cadena que no sé cómo escribir el LINQ ...

Respuestas:


192

Puedes poner lo que quieras dentro de la selectcláusula:

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

EDITAR : Puede extraer múltiples errores en elementos de lista separados agregando una fromcláusula, como esta:

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

O:

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

2 ª EDICIÓN : ¿Estás en busca de un Dictionary<string, string[]>:

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

Esa es una respuesta rápida :)! Hola, eso se ve bien, pero ¿qué pasa si ModelState [item.Key] tiene más de 1 error? Errores [0] solo funciona para un único mensaje de error
JK.

¿Cómo quieres combinarlos?
Fugas

Gracias, eso es casi todo, pero está seleccionando cada tecla incluso si no tiene errores, ¿cómo podemos filtrar las teclas sin errores?
JK.

44
Agregar.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks

3
Para obtener el mismo resultado Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);que debería usar, de lo 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());contrario no tendrá los
mensajes de

74

Aquí está la implementación completa con todas las piezas juntas:

Primero cree un método de extensión:

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

Luego llame a ese método de extensión y devuelva los errores de la acción del controlador (si corresponde) como json:

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

Y finalmente, muestre esos errores en el lado del cliente (en el estilo de jquery.validation, pero se puede cambiar fácilmente a cualquier otro 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());
    }
}

Esto parece un método interesante, sin embargo, la clase auxiliar no funciona para mí. ¿Es esto debido a cambios quizás con MVC 2? Recibo un error de que el método ToDictionary no existe en modelState.
Cymen

@Cymen, ¿te olvidas de hacer referencia a System.Linq? ToDictionary () es un método de extensión LINQ.
Nathan Taylor

8
Sujeto a sus preferencias .Where(m => m.Value.Count() > 0)también podría escribirse como .Where(m => m.Value.Any()).
Manfred

Esto se puede usar de manera similar a ModelState.ToDataSourceResult () de Kendo.Mvc para devolver errores a la cuadrícula y mostrar mensajes de error en la edición.
malnosna

22

Me gusta usar Hashtableaquí, para obtener el objeto JSON con propiedades como claves y errores como valor en forma de matriz de cadenas.

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

De esta manera obtienes la siguiente respuesta:

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

8

Hay muchas maneras diferentes de hacer esto que funcionan. Aquí está ahora lo hago ...

if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}

2
También puede regresar BadRequest(ModelState)y lo serializará en JSON por usted.
Fred

6

La forma más fácil de hacer esto es devolver un a BadRequestcon el ModelState en sí:

Por ejemplo en un PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Si usamos anotaciones de datos en, por ejemplo, un número de móvil, como este, en la Updateclase:

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

Esto devolverá lo siguiente en una solicitud no vá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."
    ]
  }
}

1
BadRequest es específico de WebAPI y esta pregunta es sobre MVC.
rgripper

5

@JK me ayudó mucho, pero ¿por qué no?

 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

Eche un vistazo a System.Web.Http.Results.OkNegotiatedContentResult.

Convierte todo lo que arrojes en JSON.

Entonces hice esto

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

return Ok(errorList);

Esto resultó en:

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

Todavía tengo que comprobar qué sucede cuando hay más de un error para cada campo, pero el punto es que OkNegoriatedContentResult es brillante.

Tengo la idea de linq / lambda de @SLaks


3

Manera simple de lograr esto mediante el uso de la funcionalidad incorporada

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

    //do something
}

El resultado de JSON será


2

ToDictionary es una extensión Enumerable que se encuentra en System.Linq empaquetada en System.Web.Extensions dll http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Así es como se ve la clase completa para mí.

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

¿Por qué no devolver el ModelStateobjeto original al cliente y luego usar jQuery para leer los valores? Para mí, parece mucho más simple y utiliza la estructura de datos común (.net'sModelState )

para devolver el ModelState como Json, simplemente páselo al constructor de la clase Json (funciona con CUALQUIER 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);
        }

1

Variación con tipo de retorno en lugar de devolver 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;
    }
}

0

Hice una extensión que devuelve una cadena con el separador "" (puede usar el suyo):

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

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

        }

si usa jsonresult, entonces regrese

return Json(Errors);

o simplemente puede devolver el modelStateErrors, no lo he probado. Lo que hice fue asignar la colección de Errores a mi ViewModel y luego hacer un bucle ... En este caso, puedo devolver mis Errores a través de json. Tengo una clase / modelo, quería obtener la fuente / clave pero todavía estoy tratando de resolverlo.

    public class ErrorList
{
    public string ErrorMessage;
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.