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.