ASP.NET MVC Come convertire gli errori ModelState in json


127

Come si ottiene un elenco di tutti i messaggi di errore di ModelState? Ho trovato questo codice per ottenere tutte le chiavi: ( Restituzione di un elenco di chiavi con errori ModelState )

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

Ma come posso ottenere i messaggi di errore come IList o IQueryable?

Potrei andare:

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

Ma questo è farlo manualmente - sicuramente c'è un modo per farlo usando LINQ? La proprietà .ErrorMessage è così lontana dalla catena che non so come scrivere LINQ ...

Risposte:


192

Puoi inserire tutto quello che vuoi all'interno della selectclausola:

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

EDIT : è possibile estrarre più errori in voci di elenco separate aggiungendo una fromclausola, in questo modo:

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 ° EDIT : stai cercando un Dictionary<string, string[]>:

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

Questa è una risposta rapida :)! Ehi, sembra buono, ma cosa succede se ModelState [item.Key] ha più di 1 errore? Errori [0] funziona solo per un singolo messaggio di errore
JK.

Come vuoi combinarli?
SLaks,

Grazie quasi - ma sta selezionando tutte le chiavi anche se non ha errori - come possiamo filtrare le chiavi senza errori?
JK.

4
Aggiungi.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks,

3
Per ottenere lo stesso output come da Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);te dovresti usare 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());Altrimenti non avrai gli ExceptionMessages
Silvos,

74

Ecco la piena implementazione con tutti i pezzi messi insieme:

Innanzitutto crea un metodo di estensione:

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

Quindi chiamare quel metodo di estensione e restituire gli errori dall'azione del controller (se presente) come json:

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

E infine, mostra quegli errori sul lato client (in stile jquery.validation, ma può essere facilmente modificato in qualsiasi altro stile)

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

Sembra un metodo interessante, tuttavia la classe helper non funziona per me. Ciò è dovuto a cambiamenti forse con MVC 2? Ricevo un errore che il metodo ToDictionary non esiste su modelState.
Cymen,

@Cymen ti stai dimenticando di fare riferimento a System.Linq? ToDictionary () è un metodo di estensione LINQ.
Nathan Taylor,

8
In base alle vostre preferenze .Where(m => m.Value.Count() > 0)potrebbe anche essere scritto come.Where(m => m.Value.Any()) .
Manfred,

Questo può essere usato in modo simile a ModelState.ToDataSourceResult () di Kendo.Mvc per restituire errori alla griglia e visualizzare messaggi di errore durante la modifica.
Malnosna,

22

Mi piace usare Hashtablequi, in modo da ottenere l'oggetto JSON con proprietà come chiavi ed errori come valore in forma di array di stringhe.

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

In questo modo ottieni la seguente risposta:

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

8

Ci sono molti modi diversi per farlo che funzionano tutti. Qui è ora lo faccio ...

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

2
Puoi anche tornare BadRequest(ModelState)e lo serializzerà in JSON per te.
Fred

6

Il modo più semplice per farlo è semplicemente restituire a BadRequest con ModelState stesso:

Ad esempio su un PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Se utilizziamo le annotazioni dei dati, ad esempio un numero di cellulare, come questo, nella Updateclasse:

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

Ciò restituirà quanto segue in una richiesta non valida:

{
  "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 è specifico di WebAPI e questa domanda riguarda MVC.
rgripper,

5

@JK mi ha aiutato molto, ma perché 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

Dai un'occhiata a System.Web.Http.Results.OkNegotiatedContentResult.

Converte tutto ciò che ci passi in JSON.

Quindi l'ho fatto

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

return Ok(errorList);

Ciò ha comportato:

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

Devo ancora verificare cosa succede quando c'è più di un errore per ogni campo, ma il punto è OkNegoriatedContentResult è geniale!

Ho avuto l'idea di linq / lambda da @SLaks


3

Modo semplice raggiungere questo obiettivo utilizzando la funzionalità integrata

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

    //do something
}

Il risultato JSON sarà


2

ToDictionary è un'estensione Enumerable trovata in System.Linq impacchettata in System.Web.Extensions dll http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Ecco come appare la classe completa per me.

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

Perché non restituire l' ModelStateoggetto originale al client, quindi utilizzare jQuery per leggere i valori. Per me sembra molto più semplice e utilizza la struttura dati comune (.net'sModelState )

per restituire il file ModelState Json come, semplicemente passarlo al costruttore della classe Json (funziona con QUALSIASI oggetto)

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

Variazione con tipo restituito anziché restituire 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

Ho creato un'estensione che restituisce una stringa con il separatore "" (puoi usare il tuo):

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

        }

se usi jsonresult, ritorna

return Json(Errors);

oppure puoi semplicemente restituire modelStateErrors, non l'ho mai provato. Quello che ho fatto è stato assegnare la raccolta Errori al mio ViewModel e quindi eseguirne il ciclo. In questo caso posso restituire i miei errori tramite json. Ho una classe / modello, volevo ottenere il codice sorgente / chiave, ma sto ancora cercando di capirlo.

    public class ErrorList
{
    public string ErrorMessage;
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.