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 ...

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

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

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

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

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

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

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


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


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

    }

}

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

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

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

-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;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.