c# - ASP.NET MVC Come convertire gli errori ModelState in json




asp.net-mvc linq (10)

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 potrei 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ì giù lungo la catena che non so come scrivere LINQ ...


Answers

Ci sono molti modi diversi per fare tutto ciò che funziona. Ecco ora lo faccio ...

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

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

per restituire ModelState come Json, basta 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);
        }

Mi piace usare Hashtable qui, in modo da ottenere oggetti 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."
      ]
   }
}

Il modo più semplice per farlo è restituire una BadRequest con lo stesso ModelState:

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 usiamo annotazioni di dati, ad esempio un numero di cellulare, come questo, nella classe Update :

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

Ecco la piena implementazione con tutti i pezzi messi insieme:

Per prima cosa 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.Count() > 0);
        }
        return null;
    }
}

Quindi chiama il metodo di estensione e restituisci 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 (nello 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());
    }
}

  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 il modelloStateErrors, non l'ho provato. Quello che ho fatto è stato assegnare la collezione Errors al mio ViewModel e quindi looparlo ... 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;
}

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

Converte qualunque cosa tu ci passi dentro a 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 provocato:

{
  "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 ricevuto l'idea di linq / lambda da @SLaks


ToDictionary è un'estensione enumerabile trovata in System.Linq impacchettata nella dll System.Web.Extensions 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;
        }

    }

}

Puoi mettere tutto ciò che vuoi all'interno della clausola select :

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 clausola from , come questa:

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

Sono stato in grado di farlo usando un po 'di LINQ,

public static List<string> GetErrorListFromModelState
                                              (ModelStateDictionary modelState)
{
      var query = from state in modelState.Values
                  from error in state.Errors
                  select error.ErrorMessage;

      var errorList = query.ToList();
      return errorList;
}

Il metodo sopra riportato restituisce un elenco di errori di convalida.

Ulteriori letture:

Come leggere tutti gli errori da ModelState in ASP.NET MVC





c# asp.net-mvc linq modelstate