Menangani Validasi ModelState di ASP.NET Web API


106

Saya bertanya-tanya bagaimana saya bisa mencapai validasi model dengan ASP.NET Web API. Saya memiliki model saya seperti ini:

public class Enquiry
{
    [Key]
    public int EnquiryId { get; set; }
    [Required]
    public DateTime EnquiryDate { get; set; }
    [Required]
    public string CustomerAccountNumber { get; set; }
    [Required]
    public string ContactName { get; set; }
}

Saya kemudian memiliki tindakan Posting di Pengontrol API saya:

public void Post(Enquiry enquiry)
{
    enquiry.EnquiryDate = DateTime.Now;
    context.DaybookEnquiries.Add(enquiry);
    context.SaveChanges();
}

Bagaimana cara menambahkan if(ModelState.IsValid)dan kemudian menangani pesan kesalahan untuk diteruskan ke pengguna?

Jawaban:


186

Untuk pemisahan perhatian, saya sarankan Anda menggunakan filter tindakan untuk validasi model, jadi Anda tidak perlu terlalu peduli bagaimana melakukan validasi di pengontrol api Anda:

using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace System.Web.Http.Filters
{
    public class ValidationActionFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var modelState = actionContext.ModelState;

            if (!modelState.IsValid)
                actionContext.Response = actionContext.Request
                     .CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
        }
    }
}

27
Ruang nama yang diperlukan untuk ini System.Net.Http, System.Net System.Web.Http.Controllersdan System.Web.Http.Filters.
Christopher Stevenson

11
Ada juga implementasi serupa di halaman Api Web ASP.NET resmi: asp.net/web-api/overview/formats-and-model-binding/…
Erik Schierboom

1
Meskipun tidak meletakkan [ValidationActionFilter] di atas api web, itu masih memanggil kode dan memberi saya permintaan buruk.
micronyks

1
Perlu diperhatikan bahwa respons error yang dikembalikan dikontrol oleh IncludeErrorDetailPolicy . Secara default, respons untuk permintaan jarak jauh hanya berisi pesan umum "Terjadi kesalahan", tetapi menyetelnya ke IncludeErrorDetailPolicy.Alwaysakan menyertakan detail (dengan risiko mengekspos detail ke pengguna Anda)
Rob

Apakah ada alasan khusus mengapa Anda tidak menyarankan menggunakan IAsyncActionFilter sebagai gantinya?
Ravior

30

Mungkin bukan yang Anda cari, tapi mungkin bagus untuk diketahui seseorang:

Jika Anda menggunakan .net Web Api 2, Anda dapat melakukan hal berikut:

if (!ModelState.IsValid)
     return BadRequest(ModelState);

Bergantung pada kesalahan model, Anda mendapatkan hasil ini:

{
   Message: "The request is invalid."
   ModelState: {
       model.PropertyA: [
            "The PropertyA field is required."
       ],
       model.PropertyB: [
             "The PropertyB field is required."
       ]
   }
}

1
Ingatlah ketika saya mengajukan pertanyaan ini Web API 1 baru saja dirilis, mungkin sudah banyak berpindah sejak saat itu :)
CallumVass

Pastikan untuk menandai properti sebagai opsional, jika tidak Anda akan mendapatkan pesan umum yang tidak membantu "Terjadi kesalahan". pesan eror.
Bouke

1
Apakah ada cara untuk mengubah Pesan?
saquib adil

29

Seperti ini misalnya:

public HttpResponseMessage Post(Person person)
{
    if (ModelState.IsValid)
    {
        PersonDB.Add(person);
        return Request.CreateResponse(HttpStatusCode.Created, person);
    }
    else
    {
        // the code below should probably be refactored into a GetModelErrors
        // method on your BaseApiController or something like that

        var errors = new List<string>();
        foreach (var state in ModelState)
        {
            foreach (var error in state.Value.Errors)
            {
                errors.Add(error.ErrorMessage);
            }
        }
        return Request.CreateResponse(HttpStatusCode.Forbidden, errors);
    }
}

Ini akan mengembalikan respons seperti ini (dengan asumsi JSON, tetapi prinsip dasar yang sama untuk XML):

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
(some headers removed here)

["A value is required.","The field First is required.","Some custom errorm essage."]

Anda tentu saja dapat membuat objek / daftar kesalahan sesuka Anda, misalnya menambahkan nama bidang, id bidang, dll.

Meskipun itu adalah panggilan Ajax "satu arah" seperti POST entitas baru, Anda masih harus mengembalikan sesuatu ke pemanggil - sesuatu yang menunjukkan apakah permintaan tersebut berhasil atau tidak. Bayangkan sebuah situs di mana pengguna Anda akan menambahkan beberapa info tentang diri mereka sendiri melalui permintaan AJAX POST. Bagaimana jika informasi yang mereka coba masukkan tidak valid - bagaimana mereka tahu jika tindakan Simpan berhasil atau tidak?

Cara terbaik untuk melakukan ini adalah menggunakan Kode Status HTTP Lama yang Baik seperti 200 OKdan seterusnya. Dengan cara itu JavaScript Anda dapat menangani kegagalan dengan benar menggunakan callback yang benar (kesalahan, sukses, dll).

Berikut tutorial yang bagus tentang versi yang lebih maju dari metode ini, menggunakan ActionFilter dan jQuery: http://asp.net/web-api/videos/getting-started/custom-validation


Itu hanya mengembalikan enquiryobjek saya , itu tidak mengatakan properti mana yang tidak valid? Jadi Jika saya dibiarkan CustomerAccountNumberkosong, itu harus mengatakan pesan validasi default (bidang CusomterAccountNumber diperlukan ..)
CallumVass

Begitu, jadi apakah ini cara yang "benar" untuk menangani Validasi Model? Sepertinya agak berantakan bagi saya ..
CallumVass

Ada cara lain untuk melakukannya juga, seperti menghubungkan dengan validasi jQuery. Berikut adalah contoh Microsoft yang bagus: asp.net/web-api/videos/getting-started/custom-validation
Anders Arpi

Metode ini dan metode yang dipilih sebagai jawaban "harus" secara fungsional identik, jadi jawaban ini memiliki nilai tambah untuk menunjukkan kepada Anda bagaimana Anda dapat melakukannya sendiri tanpa filter tindakan.
Shaun Wilson

Saya harus mengubah jalur errors.Add(error.ErrorMessage);agar errors.Add(error.Exception.Message);ini berfungsi untuk saya.
Caltor

9

8

Atau, jika Anda mencari kumpulan kesalahan sederhana untuk aplikasi Anda .. berikut adalah implementasi saya untuk ini:

public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var modelState = actionContext.ModelState;

        if (!modelState.IsValid) 
        {

            var errors = new List<string>();
            foreach (var state in modelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    errors.Add(error.ErrorMessage);
                }
            }

            var response = new { errors = errors };

            actionContext.Response = actionContext.Request
                .CreateResponse(HttpStatusCode.BadRequest, response, JsonMediaTypeFormatter.DefaultMediaType);
        }
    }

Respon Pesan Kesalahan akan terlihat seperti ini:

{
  "errors": [
    "Please enter a valid phone number (7+ more digits)",
    "Please enter a valid e-mail address"
  ]
}

5

Tambahkan kode di bawah ini di file startup.cs

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2).ConfigureApiBehaviorOptions(options =>
            {
                options.InvalidModelStateResponseFactory = (context) =>
                {
                    var errors = context.ModelState.Values.SelectMany(x => x.Errors.Select(p => new ErrorModel()
                   {
                       ErrorCode = ((int)HttpStatusCode.BadRequest).ToString(CultureInfo.CurrentCulture),
                        ErrorMessage = p.ErrorMessage,
                        ServerErrorMessage = string.Empty
                    })).ToList();
                    var result = new BaseResponse
                    {
                        Error = errors,
                        ResponseCode = (int)HttpStatusCode.BadRequest,
                        ResponseMessage = ResponseMessageConstants.VALIDATIONFAIL,

                    };
                    return new BadRequestObjectResult(result);
                };
           });

3

Di sini Anda dapat memeriksa untuk menunjukkan kesalahan status model satu per satu

 public HttpResponseMessage CertificateUpload(employeeModel emp)
    {
        if (!ModelState.IsValid)
        {
            string errordetails = "";
            var errors = new List<string>();
            foreach (var state in ModelState)
            {
                foreach (var error in state.Value.Errors)
                {
                    string p = error.ErrorMessage;
                    errordetails = errordetails + error.ErrorMessage;

                }
            }
            Dictionary<string, object> dict = new Dictionary<string, object>();



            dict.Add("error", errordetails);
            return Request.CreateResponse(HttpStatusCode.BadRequest, dict);


        }
        else
        {
      //do something
        }
        }

}


3

C #

    public class ValidateModelAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            if (actionContext.ModelState.IsValid == false)
            {
                actionContext.Response = actionContext.Request.CreateErrorResponse(
                    HttpStatusCode.BadRequest, actionContext.ModelState);
            }
        }
    }

...

    [ValidateModel]
    public HttpResponseMessage Post([FromBody]AnyModel model)
    {

Javascript

$.ajax({
        type: "POST",
        url: "/api/xxxxx",
        async: 'false',
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(data),
        error: function (xhr, status, err) {
            if (xhr.status == 400) {
                DisplayModelStateErrors(xhr.responseJSON.ModelState);
            }
        },
....


function DisplayModelStateErrors(modelState) {
    var message = "";
    var propStrings = Object.keys(modelState);

    $.each(propStrings, function (i, propString) {
        var propErrors = modelState[propString];
        $.each(propErrors, function (j, propError) {
            message += propError;
        });
        message += "\n";
    });

    alert(message);
};

2

Saya memiliki masalah dalam menerapkan pola solusi yang diterima di mana saya ModelStateFilterakan selalu mengembalikan false(dan kemudian 400) untuk actionContext.ModelState.IsValidobjek model tertentu:

public class ModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        if (!actionContext.ModelState.IsValid)
        {
            actionContext.Response = new HttpResponseMessage { StatusCode = HttpStatusCode.BadRequest};
        }
    }
}

Saya hanya menerima JSON, jadi saya menerapkan kelas pengikat model kustom:

public class AddressModelBinder : System.Web.Http.ModelBinding.IModelBinder
{
    public bool BindModel(HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext)
    {
        var posted = actionContext.Request.Content.ReadAsStringAsync().Result;
        AddressDTO address = JsonConvert.DeserializeObject<AddressDTO>(posted);
        if (address != null)
        {
            // moar val here
            bindingContext.Model = address;
            return true;
        }
        return false;
    }
}

Yang saya daftarkan langsung setelah model saya melalui

config.BindParameter(typeof(AddressDTO), new AddressModelBinder());

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.