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?

CallumVass
sumber

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);
        }
    }
}
cuongle
sumber
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."
       ]
   }
}
Apakah Almaas
sumber
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

Anders Arpi
sumber
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

Anda dapat menggunakan atribut dari System.ComponentModel.DataAnnotationsnamespace untuk menyetel aturan validasi. Lihat Validasi Model - Oleh Mike Wasson untuk detailnya.

Lihat juga video ASP.NET Web API, Bagian 5: Validasi Kustom - Jon Galloway

Referensi Lain

  1. Berjalan-jalan di Sisi Klien dengan WebAPI dan WebForms
  2. Bagaimana ASP.NET Web API mengikat pesan HTTP ke model domain, dan cara bekerja dengan format media di Web API.
  3. Dominick Baier - Mengamankan API Web ASP.NET
  4. Mengaitkan validasi AngularJS ke ASP.NET Web API Validation
  5. Menampilkan Kesalahan ModelState dengan AngularJS di ASP.NET MVC
  6. Bagaimana cara membuat kesalahan ke klien? AngularJS / WebApi ModelState
  7. Validasi Injeksi Ketergantungan di API Web
LCJ
sumber
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"
  ]
}
sandeep talabathula
sumber
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);
                };
           });
MayankGaur
sumber
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
        }
        }

}

Debendra Dash
sumber
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);
};
Nick Hermans
sumber
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());
pengguna326608
sumber