ASP.NET MVC Comment convertir les erreurs ModelState en json

127

Comment obtenir 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 qu'IList ou IQueryable?

Je pourrais aller:

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

Mais c'est le faire 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 ...

JK.
la source

Réponses:

192

Vous pouvez mettre tout ce que vous voulez dans la selectclause:

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 séparés en ajoutant une fromclause, 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();

2 ème EDIT : Vous recherchez un Dictionary<string, string[]>:

var errorList = ModelState.ToDictionary(
    kvp => kvp.Key,
    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
SLaks
la source
C'est une réponse rapide :)! Hé, ça a l'air bien, mais que faire si ModelState [item.Key] a plus d'une erreur? Erreurs [0] ne fonctionne que pour un seul message d'erreur
JK.
Comment voulez-vous les combiner?
SLaks
Merci c'est presque - mais il sélectionne chaque clé même si elle ne comporte aucune erreur - comment pouvons-nous filtrer les clés sans erreur?
JK.
4
Ajouter.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks
3
Pour obtenir le même résultat que celui de, Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);vous devez utiliser 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());Sinon, vous n'aurez pas les ExceptionMessages
Silvos
74

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

Créez d'abord 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 retournez les erreurs de l'action du contrôleur (le cas échéant) en tant que json:

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

Et puis enfin, montrez ces erreurs côté client (dans le style jquery.validation, mais peut être facilement changé en tout 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());
    }
}
JK.
la source
Cela ressemble à une méthode intéressante, mais la classe d'assistance ne fonctionne pas pour moi. Est-ce dû à des changements peut-être avec MVC 2? J'obtiens une erreur indiquant que la méthode ToDictionary n'existe pas sur modelState.
Cymen
@Cymen oubliez-vous de faire référence à System.Linq? ToDictionary () est une méthode d'extension LINQ.
Nathan Taylor
8
Sous réserve de vos préférences, il .Where(m => m.Value.Count() > 0)peut également s'écrire .Where(m => m.Value.Any()).
Manfred
Cela peut être utilisé de la même manière que ModelState.ToDataSourceResult () de Kendo.Mvc pour renvoyer des erreurs à la grille et afficher des messages d'erreur lors de l'édition.
malnosna
22

J'aime utiliser Hashtableici, de sorte que j'obtienne un objet JSON avec des propriétés comme clés et des erreurs comme valeur sous forme de 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."
      ]
   }
}
Jovica Zaric
la source
8

Il existe de nombreuses façons de faire cela qui fonctionnent toutes. Voici maintenant je le fais ...

if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}
Dean North
la source
2
Vous pouvez également revenir BadRequest(ModelState)et il le sérialisera dans JSON pour vous.
Fred
6

Le moyen le plus simple de le faire est de simplement renvoyer un BadRequestavec le ModelState lui-même:

Par exemple sur un 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 mobile, comme celui-ci, dans la Updateclasse:

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

Cela renverra ce qui suit sur une demande invalide:

{
  "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
la source
1
BadRequest est spécifique à WebAPI et cette question concerne MVC.
rgripper
5

@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;
        }
h45d6f7d4f6f
la source
3

Jetez un œil à System.Web.Http.Results.OkNegotiatedContentResult.

Il convertit tout ce que vous y jetez 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 n'ai pas encore vérifié ce qui se passe lorsqu'il y a plus d'une erreur pour chaque champ, mais le fait est que OkNegoriatedContentResult est brillant!

J'ai eu l'idée linq / lambda de @SLaks

ozzy432836
la source
3

Un moyen 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

Nisfan
la source
2

ToDictionary est une extension Enumerable trouvée dans System.Linq emballée dans la dll System.Web.Extensions http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Voici à quoi ressemble le cours complet 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;
        }

    }

}
Philrabin
la source
2

Pourquoi ne pas renvoyer l' ModelStateobjet 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 (.net ModelState)

pour retourner le ModelState as Json, passez-le simplement au constructeur de 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);
        }
d.popov
la source
1

Variation avec type de retour au lieu de renvoyer 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
la source
0

J'ai fait une extension qui retourne la chaîne avec le séparateur "" (vous pouvez utiliser le vôtre):

   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
la source
-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 vous utilisez jsonresult puis retournez

return Json(Errors);

ou vous pouvez simplement renvoyer le modelStateErrors, je ne l'ai pas essayé. Ce que j'ai fait, c'est d'assigner la collection Errors à mon ViewModel, puis de la boucler .. Dans ce cas, je peux renvoyer mes Erreurs via json. J'ai une classe / un modèle, je voulais obtenir la source / la clé mais j'essaie toujours de le comprendre.

    public class ErrorList
{
    public string ErrorMessage;
}
CyberNinja
la source