ASP.NET MVC Як конвертувати помилки ModelState в json


127

Як отримати список усіх повідомлень про помилки ModelState? Я знайшов цей код, щоб отримати всі ключі: ( Повернення списку ключів з помилками ModelState )

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

Але як я можу отримати повідомлення про помилку як IList або IQueryable?

Я міг би піти:

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

Але це робиться вручну - напевно, є спосіб зробити це за допомогою LINQ? Властивість .ErrorMessage настільки вниз по ланцюгу, що я не знаю, як написати LINQ ...

Відповіді:


192

Ви можете помістити все, що завгодно, до цього selectпункту:

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

РЕДАКТУВАННЯ : Ви можете вилучити кілька помилок в окремих елементах списку, додавши fromпункт, наприклад, такий:

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

Або:

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

2- е редагування : Ви шукаєте Dictionary<string, string[]>:

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

Ось швидка відповідь :)! Привіт, це виглядає добре, але що робити, якщо ModelState [item.Key] має більше 1 помилки? Помилки [0] працює лише для одного повідомлення про помилку
JK.

Як ви хочете їх поєднати?
СЛАкс

Завдяки цьому майже все - але підбір кожної клавіші, навіть якщо вона не має помилок - як ми можемо відфільтрувати ключі без помилок?
Ж.К.

4
Додати.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks

3
Щоб отримати такий самий вихід, як і у Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);вас, слід використати 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());інакше у вас не буде повідомлень про виняток
Silvos

74

Ось повна реалізація всіх складених деталей:

Спочатку створіть метод розширення:

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

Потім зателефонуйте до цього методу розширення та поверніть помилки з дії контролера (якщо такі є) як json:

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

І, нарешті, покажіть ці помилки на стороні клієнта (у стилі jquery.validation, але їх можна легко змінити на будь-який інший стиль)

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

Це виглядає як цікавий метод, однак клас помічників для мене не працює. Це пов'язано зі змінами, можливо, з MVC 2? Я отримую помилку про те, що методу ToDictionary не існує на modelState.
Кімен

@Cymen Ви забуваєте посилатися на System.Linq? ToDictionary () - метод розширення LINQ.
Натан Тейлор

8
З урахуванням ваших уподобань .Where(m => m.Value.Count() > 0)можна також записати як .Where(m => m.Value.Any()).
Манфред

Це може використовуватися аналогічно як ModelState.ToDataSourceResult () від Kendo.Mvc для повернення помилок до Grid та відображення повідомлень про помилки під час редагування.
мальносна

22

Мені подобається використовувати Hashtableтут, щоб я отримав об'єкт JSON з властивостями як ключі та помилками як значення у вигляді масиву рядків.

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

Таким чином ви отримуєте наступну відповідь:

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

8

Існує маса різних способів зробити це, щоб усі працювали. Ось зараз я це роблю ...

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

2
Ви також можете повернутися, BadRequest(ModelState)і це буде серіалізовувати його в JSON для вас.
Фред

6

Найпростіший спосіб зробити це - просто повернути a BadRequestіз самою ModelState:

Наприклад на PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Якщо ми використовуємо примітки про дані, наприклад, наприклад, мобільний номер у Updateкласі:

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

У результаті недійсного запиту повернеться наступне:

{
  "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 специфічний для WebAPI, і це питання стосується MVC.
rgripper

5

@JK це мені дуже допомогло, але чому б ні:

 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

Погляньте на System.Web.Http.Results.OkNegotiatedContentResult.

Він перетворює все, що ви кинете в нього, на JSON.

Тому я це зробив

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

return Ok(errorList);

Це призвело до:

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

Я ще повинен перевірити, що відбувається, коли в кожному полі є більше однієї помилки, але справа в тому, що OkNegoriatedContentResult є блискучим!

Отримав ідею linq / lambda від @SLaks


3

Простий спосіб досягти цього за допомогою вбудованої функціональності

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

    //do something
}

Результат JSON буде


2

ToDictionary - це безліч розширень, знайдених у System.Linq, упакованих у dll System.Web.Extensions http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Ось як виглядає для мене повний клас.

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

Чому б не повернути оригінальному ModelStateоб'єкту клієнту, а потім використовувати jQuery для читання значень. Для мене це виглядає набагато простіше і використовує загальну структуру даних (.net's)ModelState )

повернути ModelState як Json, просто передайте його конструктору класу Json (працює з будь-яким об’єктом)

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

Варіація з типом повернення замість повернення 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

Я зробив і розширення, яке повертає рядок із сеператором "" (ви можете використовувати свій власний):

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

        }

якщо ви використовуєте jsonresult, тоді поверніться

return Json(Errors);

або ви можете просто повернути modelStateErrors, я його не пробував. Що я зробив, це призначити колекцію помилок моєму ViewModel, а потім зациклювати її. У цьому випадку я можу повернути свої помилки через json. У мене є клас / модель, я хотів отримати джерело / ключ, але я все ще намагаюся це з'ясувати.

    public class ErrorList
{
    public string ErrorMessage;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.