web-dev-qa-db-fra.com

ASP.NET MVC Comment convertir les erreurs ModelState en json

Comment obtenez-vous une liste de tous les messages d'erreur ModelState? J'ai trouvé ce code pour obtenir toutes les clés: ( Retour d'une liste de clés avec des erreurs ModelState )

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

Mais comment pourrais-je obtenir les messages d'erreur en tant que IList ou IQueryable?

Je pourrais aller:

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

Mais c’est fait manuellement - il existe sûrement un moyen de le faire en utilisant LINQ? La propriété .ErrorMessage est si loin dans la chaîne que je ne sais pas comment écrire le LINQ ...

116
JK.

Vous pouvez mettre n'importe quoi dans la clause select:

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

EDIT: Vous pouvez extraire plusieurs erreurs dans des éléments de liste distincts en ajoutant une clause from, comme ceci:

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

2dakota du Nord EDIT : Vous recherchez un Dictionary<string, string[]>:

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

Voici la mise en œuvre complète avec toutes les pièces réunies: 

Commencez par créer une méthode d'extension:

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

Appelez ensuite cette méthode d’extension et renvoyez les erreurs de l’action du contrôleur (le cas échéant) sous forme de json:

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

Et enfin, affichez ces erreurs du côté client (dans le style jquery.validation, mais peut être facilement remplacé par un autre style)

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());
    }
}
71
JK.

J'aime utiliser Hashtable ici, afin d'obtenir un objet JSON avec des propriétés en tant que clés et des erreurs en tant que valeur sous la forme d'un tableau de chaînes.

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 cette façon, vous obtenez la réponse suivante:

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

Il y a beaucoup de façons différentes de faire cela qui fonctionnent. Voici maintenant je le fais ...

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

@JK cela m'a beaucoup aidé mais pourquoi pas: 

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

Pour ce faire, le plus simple consiste à renvoyer une BadRequest avec le ModelState lui-même:

Par exemple sur une PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Si nous utilisons des annotations de données sur, par exemple, un numéro de téléphone, comme celui-ci, dans la classe Update:

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

Cela renverra le texte suivant sur une requête non valide:

{
  "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."
    ]
  }
}
3

Jetez un coup d'oeil à System.Web.Http.Results.OkNegotiatedContentResult.

Il convertit tout ce que vous y mettez en JSON.

Alors j'ai fait ça

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

return Ok(errorList);

Cela a abouti à:

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

Je suis encore à vérifier ce qui se passe quand il y a plus d'une erreur pour chaque champ mais le point est que OkNegoriatedContentResult est génial!

Vous avez eu l'idée de linq/lambda de @SLaks

3
ozzy432836

ToDictionary est une extension Enumerable trouvée dans System.Linq et fournie dans la DLL System.Web.Extensions http://msdn.Microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Voici à quoi ressemble la classe complète pour moi.

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
philrabin

Pourquoi ne pas renvoyer l'objet ModelState d'origine au client, puis utiliser jQuery pour lire les valeurs. Pour moi, cela semble beaucoup plus simple et utilise la structure de données commune (ModelState de .net)

pour renvoyer la ModelState en Json, transmettez-la simplement au constructeur de la classe Json (fonctionne avec n'importe quel objet)

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

Manière simple d'y parvenir en utilisant la fonctionnalité intégrée

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

    //do something
}

Le résultat JSON sera

2
Nisfan

Variation avec type de retour au lieu de retourner 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;
    }
}
1
Jeff Circeo

J'ai fait et extension qui retourne string avec seperator "" (vous pouvez utiliser votre propre) 

   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);
    }
0
Niyaz Mukhamedya