ASP.NET MVC Jak przekonwertować błędy ModelState na json

127

Jak uzyskać listę wszystkich komunikatów o błędach ModelState? Znalazłem ten kod, aby uzyskać wszystkie klucze: ( Zwracanie listy kluczy z błędami ModelState )

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

Ale jak uzyskać komunikaty o błędach jako IList lub IQueryable?

Mógłbym pójść:

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

Ale to robi to ręcznie - na pewno jest sposób, aby to zrobić za pomocą LINQ? Właściwość .ErrorMessage jest tak daleko w łańcuchu, że nie wiem, jak napisać LINQ ...

JK.
źródło

Odpowiedzi:

192

W klauzuli możesz umieścić wszystko, co chcesz select:

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

EDYCJA : Możesz wyodrębnić wiele błędów do oddzielnych elementów listy, dodając fromklauzulę, na przykład:

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

Lub:

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

2 nd EDIT : Ty szukasz Dictionary<string, string[]>:

var errorList = ModelState.ToDictionary(
    kvp => kvp.Key,
    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
SLaks
źródło
To szybka odpowiedź :)! Hej, wygląda dobrze, ale co, jeśli ModelState [item.Key] ma więcej niż 1 błąd? Błędy [0] działają tylko w przypadku pojedynczego komunikatu o błędzie
JK.
Jak chcesz je łączyć?
SLaks
Dzięki temu prawie wszystko - ale wybiera każdy klucz, nawet jeśli nie ma błędów - jak możemy odfiltrować klucze bez błędów?
JK.
4
Dodaj.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks
3
Aby uzyskać takie same wyniki, jak z Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);, należy użyć 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());W przeciwnym razie nie będziesz mieć wiadomości wyjątkowych
Silvos.
74

Oto pełna realizacja ze wszystkimi połączonymi elementami:

Najpierw utwórz metodę rozszerzenia:

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

Następnie wywołaj tę metodę rozszerzenia i zwróć błędy z akcji kontrolera (jeśli istnieją) jako json:

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

Na koniec pokaż te błędy po stronie klienta (w stylu jquery.validation, ale można je łatwo zmienić na dowolny inny styl)

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.
źródło
Wygląda to na interesującą metodę, ale klasa pomocnicza nie działa dla mnie. Czy jest to spowodowane zmianami w MVC 2? Otrzymuję błąd, że metoda ToDictionary nie istnieje w modelState.
Cymen
@Cymen zapomniałeś odwołać się do System.Linq? ToDictionary () to metoda rozszerzenia LINQ.
Nathan Taylor
8
W zależności od preferencji .Where(m => m.Value.Count() > 0)można również zapisać jako .Where(m => m.Value.Any()).
Manfred,
Może być używany podobnie jak ModelState.ToDataSourceResult () z Kendo.Mvc do zwracania błędów do siatki i wyświetlania komunikatów o błędach podczas edycji.
malnosna
22

Lubię Hashtabletutaj używać , aby uzyskać obiekt JSON z właściwościami jako kluczami i błędami jako wartością w postaci tablicy ciągów.

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

W ten sposób otrzymasz następującą odpowiedź:

{
   "success":false,
   "errors":{
      "Phone":[
         "The Phone field is required."
      ]
   }
}
Jovica Zaric
źródło
8

Można to zrobić na wiele różnych sposobów, z których wszystkie działają. Oto teraz robię to ...

if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}
Dean North
źródło
2
Możesz także zwrócić, BadRequest(ModelState)a zostanie on serializowany do formatu JSON za Ciebie.
Fred
6

Najłatwiejszym sposobem jest po prostu zwrócenie a BadRequestz samym ModelState:

Na przykład na PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Jeśli używamy adnotacji danych np. Na numerze telefonu komórkowego, jak ten, na Updatezajęciach:

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

W przypadku nieprawidłowego żądania zwróci to następujące informacje:

{
  "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
źródło
1
BadRequest jest specyficzne dla WebAPI i to pytanie dotyczy MVC.
rgripper
5

@JK bardzo mi to pomogło, ale czemu nie:

 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
źródło
3

Spójrz na System.Web.Http.Results.OkNegotiatedContentResult.

Konwertuje wszystko, co do niego wrzucisz, na JSON.

Więc zrobiłem to

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

return Ok(errorList);

Spowodowało to:

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

Jeszcze nie sprawdziłem, co się dzieje, gdy w każdym polu występuje więcej niż jeden błąd, ale chodzi o to, że OkNegoriatedContentResult jest genialny!

Pomysł na linq / lambda z @SLaks

ozzy432836
źródło
3

Prosty sposób na osiągnięcie tego dzięki wbudowanej funkcjonalności

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

    //do something
}

Wynik JSON będzie

Nisfan
źródło
2

ToDictionary to Enumerable rozszerzenie znalezione w System.Linq spakowane w bibliotece dll System.Web.Extensions http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Oto jak wygląda dla mnie cała klasa.

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
źródło
2

Dlaczego nie zwrócić oryginalnego ModelStateobiektu klientowi, a następnie użyć jQuery do odczytania wartości. Wydaje mi się, że wygląda to na znacznie prostsze i wykorzystuje wspólną strukturę danych (.net ModelState)

aby zwrócić ModelStatejako Json, po prostu przekaż go do konstruktora klasy Json (działa z DOWOLNYM obiektem)

DO#:

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
źródło
1

Odmiana z typem zwracanym zamiast zwracania 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
źródło
0

Zrobiłem rozszerzenie, które zwraca ciąg z separatorem „” (możesz użyć własnego):

   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
źródło
-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);

        }

jeśli używasz jsonresult, to return

return Json(Errors);

lub możesz po prostu zwrócić modelStateErrors, nie próbowałem tego. To, co zrobiłem, to przypisanie kolekcji Errors do mojego ViewModel, a następnie zapętlenie jej. W takim przypadku mogę zwrócić moje błędy za pośrednictwem json. Mam klasę / model, chciałem uzyskać źródło / klucz, ale wciąż próbuję to rozgryźć.

    public class ErrorList
{
    public string ErrorMessage;
}
CyberNinja
źródło