ASP.NET MVC So konvertieren Sie ModelState-Fehler in json

127

Wie erhalten Sie eine Liste aller ModelState-Fehlermeldungen? Ich habe diesen Code gefunden, um alle Schlüssel zu erhalten: ( Rückgabe einer Liste von Schlüsseln mit ModelState-Fehlern )

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

Aber wie würde ich die Fehlermeldungen als IList oder IQueryable erhalten?

Ich könnte gehen:

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

Aber das geht manuell - gibt es doch eine Möglichkeit, es mit LINQ zu machen? Die .ErrorMessage-Eigenschaft befindet sich so weit unten in der Kette, dass ich nicht weiß, wie ich den LINQ schreiben soll ...

JK.
quelle

Antworten:

191

Sie können alles , was Sie wollen, in die selectKlausel einfügen:

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

BEARBEITEN : Sie können mehrere Fehler in separate Listenelemente extrahieren, indem Sie eine fromKlausel wie die folgende hinzufügen :

var errorList = (from item in ModelState.Values
        from error in item.Errors
        select error.ErrorMessage).ToList();

Oder:

var errorList = ModelState.Values.SelectMany(m => m.Errors)
                                 .Select(e => e.ErrorMessage)
                                 .ToList();

2 nd EDIT : Sie suchen ein Dictionary<string, string[]>:

var errorList = ModelState.ToDictionary(
    kvp => kvp.Key,
    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
SLaks
quelle
Das ist eine schnelle Antwort :)! Hey, das sieht gut aus, aber was ist, wenn ModelState [item.Key] mehr als 1 Fehler hat? Fehler [0] funktionieren nur für eine einzelne Fehlermeldung
JK.
Wie möchten Sie sie kombinieren?
SLaks
Danke, das ist fast alles - aber es wählt jeden Schlüssel aus, auch wenn er keine Fehler enthält - wie können wir die Schlüssel ohne Fehler herausfiltern?
JK.
4
Hinzufügen.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks
3
Um die gleiche Ausgabe wie von Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);Ihnen zu erhalten, sollten Sie verwenden. 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());Andernfalls haben Sie nicht die ExceptionMessages
Silvos
74

Hier ist die vollständige Implementierung mit allen Teilen zusammen:

Erstellen Sie zunächst eine Erweiterungsmethode:

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

Rufen Sie dann diese Erweiterungsmethode auf und geben Sie die Fehler aus der Controller-Aktion (falls vorhanden) als json zurück:

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

Zeigen Sie diese Fehler schließlich auf der Clientseite an (im Stil jquery.validation, kann aber problemlos in einen anderen Stil geändert werden).

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.
quelle
Dies sieht nach einer interessanten Methode aus, aber die Hilfsklasse funktioniert bei mir nicht. Liegt das vielleicht an Änderungen mit MVC 2? Ich erhalte die Fehlermeldung, dass die ToDictionary-Methode in modelState nicht vorhanden ist.
Cymen
@Cymen vergessen Sie, auf System.Linq zu verweisen? ToDictionary () ist eine LINQ-Erweiterungsmethode.
Nathan Taylor
8
Vorbehaltlich Ihrer Präferenzen .Where(m => m.Value.Count() > 0)könnte auch geschrieben werden als .Where(m => m.Value.Any()).
Manfred
Dies kann ähnlich wie ModelState.ToDataSourceResult () von Kendo.Mvc verwendet werden, um Fehler an das Grid zurückzugeben und Fehlermeldungen beim Bearbeiten anzuzeigen.
Malnosna
22

Ich verwende Hashtablehier gerne , damit ich ein JSON-Objekt mit Eigenschaften als Schlüssel und Fehlern als Wert in Form eines String-Arrays erhalte.

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

Auf diese Weise erhalten Sie folgende Antwort:

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

Es gibt viele verschiedene Möglichkeiten, dies zu tun, die alle funktionieren. Hier ist jetzt mache ich es ...

if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}
Dean North
quelle
2
Sie können auch zurückkehren BadRequest(ModelState)und es wird für Sie in JSON serialisiert.
Fred
6

Der einfachste Weg, dies zu tun, besteht darin, a BadRequestmit dem ModelState selbst zurückzugeben:

Zum Beispiel auf einem PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Wenn wir Datenanmerkungen beispielsweise auf einer Handynummer wie dieser in der UpdateKlasse verwenden:

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

Dies gibt bei einer ungültigen Anforderung Folgendes zurück:

{
  "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
quelle
1
BadRequest ist WebAPI-spezifisch und diese Frage bezieht sich auf MVC.
Rgripper
5

@JK es hat mir sehr geholfen aber warum nicht:

 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
quelle
3

Schauen Sie sich System.Web.Http.Results.OkNegotiatedContentResult an.

Es konvertiert alles, was Sie hineinwerfen, in JSON.

Also habe ich das gemacht

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

return Ok(errorList);

Dies führte zu:

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

Ich muss noch überprüfen, was passiert, wenn es mehr als einen Fehler für jedes Feld gibt, aber der Punkt ist, dass OkNegoriatedContentResult brillant ist!

Habe die Linq / Lambda-Idee von @SLaks

ozzy432836
quelle
3

Dies erreichen Sie auf einfache Weise mithilfe der integrierten Funktionalität

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

    //do something
}

JSON-Ergebnis wird sein

Nisfan
quelle
2

ToDictionary ist eine Enumerable-Erweiterung in System.Linq, die in der System.Web.Extensions-DLL http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx enthalten ist . So sieht die komplette Klasse für mich aus.

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
quelle
2

Warum nicht das ursprüngliche ModelStateObjekt an den Client zurückgeben und dann mit jQuery die Werte lesen? Für mich sieht es viel einfacher aus und verwendet die gemeinsame Datenstruktur (.net's ModelState)

die ModelStateUm als Json zurückzugeben, übergeben Sie es einfach an den Json-Klassenkonstruktor (funktioniert mit JEDEM Objekt)

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
quelle
1

Variation mit Rückgabetyp anstelle von 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
quelle
0

Ich habe eine Erweiterung erstellt, die einen String mit Trennzeichen "" zurückgibt (Sie können Ihren eigenen verwenden):

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

        }

Wenn Sie jsonresult verwenden, kehren Sie zurück

return Json(Errors);

oder Sie können einfach die modelStateErrors zurückgeben, ich habe es nicht versucht. Ich habe die Errors-Auflistung meinem ViewModel zugewiesen und sie dann wiederholt. In diesem Fall kann ich meine Errors über json zurückgeben. Ich habe eine Klasse / ein Modell, ich wollte die Quelle / den Schlüssel haben, aber ich versuche immer noch, es herauszufinden.

    public class ErrorList
{
    public string ErrorMessage;
}
CyberNinja
quelle