จัดการ ModelState Validation ใน ASP.NET Web API


106

ฉันสงสัยว่าฉันจะตรวจสอบโมเดลด้วย ASP.NET Web API ได้อย่างไร ฉันมีแบบจำลองของฉันดังนี้:

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

จากนั้นฉันมีการดำเนินการโพสต์ในตัวควบคุม API ของฉัน:

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

ฉันจะเพิ่มif(ModelState.IsValid)และจัดการกับข้อความแสดงข้อผิดพลาดเพื่อส่งต่อไปยังผู้ใช้ได้อย่างไร

คำตอบ:


186

สำหรับการแยกข้อกังวลฉันขอแนะนำให้คุณใช้ตัวกรองการดำเนินการสำหรับการตรวจสอบความถูกต้องของโมเดลดังนั้นคุณจึงไม่จำเป็นต้องดูแลวิธีการตรวจสอบความถูกต้องในตัวควบคุม api ของคุณมากนัก:

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
namespaces ที่จำเป็นสำหรับการนี้System.Net.Http, และSystem.Net System.Web.Http.Controllers System.Web.Http.Filters
Christopher Stevenson

11
นอกจากนี้ยังมีการใช้งานที่คล้ายกันในหน้า ASP.NET Web Api อย่างเป็นทางการ: asp.net/web-api/overview/formats-and-model-binding/…
Erik Schierboom

1
แม้ว่าอย่าใส่ [ValidationActionFilter] ไว้เหนือ web api แต่ก็ยังเรียกรหัสและส่งคำขอที่ไม่ถูกต้องให้ฉัน
micronyks

1
มันคุ้มค่าที่ชี้ให้เห็นว่าการตอบสนองต่อข้อผิดพลาดกลับถูกควบคุมโดยIncludeErrorDetailPolicy โดยค่าเริ่มต้นการตอบกลับคำขอระยะไกลจะมีเฉพาะข้อความ "เกิดข้อผิดพลาด" ทั่วไป แต่การตั้งค่านี้IncludeErrorDetailPolicy.Alwaysจะรวมรายละเอียดไว้ด้วย (เสี่ยงต่อการเปิดเผยรายละเอียดแก่ผู้ใช้ของคุณ)
Rob

มีเหตุผลเฉพาะหรือไม่ที่คุณไม่แนะนำให้ใช้ IAsyncActionFilter แทน
Ravior

30

อาจจะไม่ใช่สิ่งที่คุณกำลังมองหา แต่อาจจะดีสำหรับคนที่รู้จัก:

หากคุณใช้. net Web Api 2 คุณสามารถทำสิ่งต่อไปนี้:

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

ขึ้นอยู่กับข้อผิดพลาดของโมเดลคุณจะได้รับผลลัพธ์นี้:

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

1
นึกขึ้นได้เมื่อฉันถามคำถามนี้ Web API 1 เพิ่งเปิดตัวมันอาจจะถูกย้ายไปมากตั้งแต่นั้นมา :)
CallumVass

อย่าลืมทำเครื่องหมายคุณสมบัติเป็นตัวเลือกมิฉะนั้นคุณจะได้รับข้อความทั่วไปที่ไม่เป็นประโยชน์ "เกิดข้อผิดพลาด" ข้อความผิดพลาด.
Bouke

1
มีวิธีเปลี่ยนข้อความหรือไม่?
saquib adil

29

เช่นนี้:

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

สิ่งนี้จะส่งคืนการตอบสนองเช่นนี้ (สมมติว่าเป็น JSON แต่หลักการพื้นฐานเดียวกันสำหรับ 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."]

แน่นอนคุณสามารถสร้างวัตถุ / รายการข้อผิดพลาดของคุณในแบบที่คุณต้องการตัวอย่างเช่นการเพิ่มชื่อเขตข้อมูลรหัสเขตข้อมูลเป็นต้น

แม้ว่าจะเป็นการเรียก Ajax แบบ "ทางเดียว" เหมือนโพสต์ของเอนทิตีใหม่คุณก็ยังควรส่งคืนบางอย่างให้กับผู้โทรซึ่งเป็นสิ่งที่บ่งบอกว่าคำขอนั้นสำเร็จหรือไม่ ลองนึกภาพไซต์ที่ผู้ใช้ของคุณจะเพิ่มข้อมูลเกี่ยวกับตัวเองผ่านคำขอ AJAX POST จะเกิดอะไรขึ้นหากข้อมูลที่พยายามป้อนไม่ถูกต้องพวกเขาจะรู้ได้อย่างไรว่าการดำเนินการบันทึกสำเร็จหรือไม่

วิธีที่ดีที่สุดคือการใช้รหัสสถานะ HTTP แบบเก่าที่ดีเช่น200 OKและอื่น ๆ ด้วยวิธีนี้ JavaScript ของคุณสามารถจัดการกับความล้มเหลวได้อย่างเหมาะสมโดยใช้การเรียกกลับที่ถูกต้อง (ข้อผิดพลาดความสำเร็จ ฯลฯ )

นี่คือบทช่วยสอนที่ดีเกี่ยวกับวิธีการนี้ในเวอร์ชันขั้นสูงโดยใช้ ActionFilter และ jQuery: http://asp.net/web-api/videos/getting-started/custom-validation


นั่นแค่ส่งคืนenquiryวัตถุของฉันแต่ไม่ได้บอกว่าคุณสมบัติใดที่ไม่ถูกต้อง? ดังนั้นหากฉันCustomerAccountNumberเว้นว่างไว้ควรระบุข้อความการตรวจสอบความถูกต้องเริ่มต้น (ต้องระบุช่อง CusomterAccountNumber .. )
CallumVass

ฉันเข้าใจแล้วนี่เป็นวิธีที่ "ถูกต้อง" ในการจัดการ Model Validation หรือไม่ ดูเหมือนจะยุ่งไปหน่อยสำหรับฉัน ..
CallumVass

มีวิธีอื่น ๆ ในการทำเช่นกันเช่นการเชื่อมต่อกับการตรวจสอบ jQuery นี่คือตัวอย่างที่ดีของ Microsoft: asp.net/web-api/videos/getting-started/custom-validation
Anders

วิธีนี้และวิธีการที่ได้รับเลือกเป็นคำตอบ "ควรจะ" เหมือนกันในการทำงานดังนั้นคำตอบนี้จึงมีมูลค่าเพิ่มในการแสดงให้คุณเห็นว่าคุณจะทำมันด้วยตัวเองได้อย่างไรโดยไม่มีตัวกรองการดำเนินการ
Shaun Wilson

ฉันต้องเปลี่ยนสายงานerrors.Add(error.ErrorMessage);เพื่อerrors.Add(error.Exception.Message);ให้งานนี้เหมาะกับฉัน
Caltor

9

8

หรือหากคุณกำลังมองหาคอลเล็กชันข้อผิดพลาดง่ายๆสำหรับแอปของคุณ .. นี่คือการใช้งานของฉัน:

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

การตอบกลับข้อความแสดงข้อผิดพลาดจะมีลักษณะดังนี้:

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

5

เพิ่มโค้ดด้านล่างในไฟล์ 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

คุณสามารถตรวจสอบเพื่อแสดงข้อผิดพลาดสถานะโมเดลทีละรายการได้ที่นี่

 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

ค#

    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

ฉันมีปัญหาในการใช้รูปแบบโซลูชันที่ยอมรับซึ่งฉันModelStateFilterจะส่งคืนเสมอfalse(และต่อมาเป็น 400) สำหรับactionContext.ModelState.IsValidวัตถุโมเดลบางอย่าง:

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

ฉันยอมรับเฉพาะ JSON ดังนั้นฉันจึงใช้คลาส Binder แบบกำหนดเอง:

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

ซึ่งฉันลงทะเบียนโดยตรงหลังจากรุ่นของฉันผ่าน

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.