ASP.NET MVC Bagaimana mengkonversi kesalahan ModelState ke json

127

Bagaimana Anda mendapatkan daftar semua pesan kesalahan ModelState? Saya menemukan kode ini untuk mendapatkan semua kunci: ( Mengembalikan daftar kunci dengan kesalahan ModelState )

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

Tetapi bagaimana saya bisa mendapatkan pesan kesalahan sebagai IList atau IQueryable?

Saya bisa pergi:

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

Tapi itu melakukannya secara manual - pasti ada cara untuk melakukannya menggunakan LINQ? Properti .ErrorMessage begitu jauh hingga saya tidak tahu cara menulis LINQ ...

JK.
sumber

Jawaban:

192

Anda dapat memasukkan apa pun yang Anda inginkan ke dalam selectklausa:

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

EDIT : Anda dapat mengekstraksi banyak kesalahan ke dalam item daftar terpisah dengan menambahkan fromklausa, seperti ini:

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

Atau:

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

2 nd EDIT : Anda sedang mencari Dictionary<string, string[]>:

var errorList = ModelState.ToDictionary(
    kvp => kvp.Key,
    kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
Slaks
sumber
Itu jawaban cepat :)! Hei itu terlihat bagus, tetapi bagaimana jika ModelState [item.Key] memiliki lebih dari 1 kesalahan? Kesalahan [0] hanya berfungsi untuk satu pesan kesalahan
JK.
Bagaimana Anda ingin menggabungkannya?
SLaks
Terima kasih sudah hampir itu - tetapi memilih setiap kunci bahkan jika tidak ada kesalahan - bagaimana kita bisa menyaring kunci tanpa kesalahan?
JK.
4
Add.Where(kvp => kvp.Value.Errors.Count > 0)
SLaks
3
Untuk mendapatkan output yang sama seperti dari Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);yang harus Anda gunakan 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());Jika tidak, Anda tidak akan memiliki ExceptionMessages
Silvos
74

Inilah implementasi penuh dengan semua bagian disatukan:

Pertama-tama buat metode ekstensi:

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

Kemudian panggil metode ekstensi itu dan kembalikan kesalahan dari tindakan pengontrol (jika ada) sebagai json:

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

Dan akhirnya, tunjukkan kesalahan tersebut di sisi klien (dalam gaya jquery.validation, tetapi dapat dengan mudah diubah ke gaya lain)

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.
sumber
Ini terlihat seperti metode yang menarik namun kelas pembantu tidak bekerja untuk saya. Apakah ini karena perubahan mungkin dengan MVC 2? Saya mendapatkan kesalahan bahwa metode ToDictionary tidak ada di modelState.
Cymen
@Cymen Anda lupa referensi System.Linq? ToDictionary () adalah metode ekstensi LINQ.
Nathan Taylor
8
Tunduk pada preferensi Anda .Where(m => m.Value.Count() > 0)juga dapat ditulis sebagai .Where(m => m.Value.Any()).
Manfred
Ini dapat digunakan dengan cara yang sama sebagai ModelState.ToDataSourceResult () dari Kendo.Mvc untuk mengembalikan kesalahan ke Grid dan menampilkan pesan kesalahan dalam pengeditan.
malnosna
22

Saya suka menggunakan di Hashtablesini, sehingga saya mendapatkan objek JSON dengan properti sebagai kunci dan kesalahan sebagai nilai dalam bentuk array string.

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

Dengan cara ini Anda mendapatkan respons berikut:

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

Ada banyak cara berbeda untuk melakukan ini agar semuanya berfungsi. Di sini sekarang saya melakukannya ...

if (ModelState.IsValid)
{
    return Json("Success");
}
else
{
    return Json(ModelState.Values.SelectMany(x => x.Errors));
}
Dean North
sumber
2
Anda juga dapat kembali BadRequest(ModelState)dan serialiaze ke JSON untuk Anda.
Fred
6

Cara termudah untuk melakukan ini adalah hanya mengembalikan a BadRequest dengan ModelState itu sendiri:

Misalnya pada PUT:

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

    // perform the update

    return StatusCode(HttpStatusCode.NoContent);
}

Jika kami menggunakan anotasi data pada mis. Nomor ponsel, seperti ini, di Updatekelas:

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

Ini akan mengembalikan yang berikut pada permintaan yang tidak valid:

{
  "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
sumber
1
BadRequest adalah spesifik WebAPI dan pertanyaan ini tentang MVC.
rgripper
5

@ JK sangat membantu saya tetapi mengapa tidak:

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

Lihatlah System.Web.Http.Results.OkNegotiatedContentResult.

Itu mengkonversi apapun yang Anda masukkan ke JSON.

Jadi saya melakukan ini

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

return Ok(errorList);

Ini menghasilkan:

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

Saya belum memeriksa apa yang terjadi ketika ada lebih dari satu kesalahan untuk setiap bidang tetapi intinya adalah OkNegoriatedContentResult brilian!

Dapatkan ide linq / lambda dari @SLaks

ozzy432836
sumber
3

Cara sederhana untuk mencapai ini dengan menggunakan fungsionalitas bawaan

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

    //do something
}

Hasil JSON akan menjadi

Nisfan
sumber
2

ToDictionary adalah ekstensi Enumerable yang ditemukan di System.Linq yang dikemas dalam System.Web.Extensions dll http://msdn.microsoft.com/en-us/library/system.linq.enumerable.todictionary.aspx . Inilah yang terlihat seperti kelas lengkap bagi saya.

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

Mengapa tidak mengembalikan ModelStateobjek asli ke klien, lalu gunakan jQuery untuk membaca nilai. Bagi saya itu terlihat lebih sederhana, dan menggunakan struktur data umum (.net ModelState)

untuk mengembalikan ModelState as Json, cukup kirimkan ke konstruktor kelas Json (berfungsi dengan objek APA PUN)

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);
        }
dpopov
sumber
1

Variasi dengan tipe pengembalian alih-alih mengembalikan 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
sumber
0

Saya membuat dan ekstensi yang mengembalikan string dengan "" separator (Anda dapat menggunakan milik Anda sendiri):

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

        }

jika Anda menggunakan jsonresult kemudian kembali

return Json(Errors);

atau Anda cukup mengembalikan modelStateErrors, saya belum mencobanya. Apa yang saya lakukan adalah menetapkan koleksi Kesalahan ke ViewModel saya dan kemudian loop itu .. Dalam hal ini saya dapat mengembalikan Kesalahan saya melalui json. Saya memiliki kelas / model, saya ingin mendapatkan sumber / kunci tetapi saya masih mencoba mencari tahu.

    public class ErrorList
{
    public string ErrorMessage;
}
CyberNinja
sumber