Xử lý xác thực trạng thái mô hình trong API web ASP.NET


106

Tôi đã tự hỏi làm thế nào tôi có thể đạt được xác thực mô hình với ASP.NET Web API. Tôi có mô hình của mình như vậy:

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

Sau đó, tôi có một hành động Đăng trong Bộ điều khiển API của mình:

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

Làm cách nào để thêm if(ModelState.IsValid)và sau đó xử lý thông báo lỗi để chuyển cho người dùng?

Câu trả lời:


186

Để tách khỏi mối quan tâm, tôi khuyên bạn nên sử dụng bộ lọc hành động để xác thực mô hình, vì vậy bạn không cần quan tâm nhiều đến cách thực hiện xác thực trong bộ điều khiển api của mình:

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
Các không gian tên cần thiết cho điều này là System.Net.Http, System.Net System.Web.Http.Controllers, và System.Web.Http.Filters.
Christopher Stevenson,

11
Ngoài ra còn có một thực hiện tương tự tại trang chính thức ASP.NET Web Api: asp.net/web-api/overview/formats-and-model-binding/...
Erik Schierboom

1
Ngay cả khi không đặt [ValidationActionFilter] trên api web, nó vẫn gọi mã và đưa ra yêu cầu xấu cho tôi.
micronyks

1
Cần chỉ ra rằng phản hồi lỗi được trả về được kiểm soát bởi Bao gồmErrorDetailPolicy . Theo mặc định, đáp ứng với yêu cầu từ xa chỉ chứa một thông điệp chung chung "Một lỗi đã xảy ra", nhưng thiết lập này để IncludeErrorDetailPolicy.Alwayssẽ bao gồm các chi tiết (ít nguy cơ bị phơi bày chi tiết để người dùng của bạn)
Rob

Có lý do cụ thể nào khiến bạn không đề xuất sử dụng IAsyncActionFilter thay thế không?
Ravior

30

Có thể không phải những gì bạn đang tìm kiếm, nhưng có lẽ thật tuyệt khi ai đó biết:

Nếu bạn đang sử dụng .net Web Api 2, bạn chỉ có thể làm như sau:

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

Tùy thuộc vào lỗi mô hình, bạn nhận được kết quả này:

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

1
Hãy ghi nhớ khi tôi hỏi câu hỏi này Web API 1 mới được phát hành, có lẽ nó đã được chuyển đi rất nhiều kể từ đó :)
CallumVass

Đảm bảo đánh dấu các thuộc tính là tùy chọn, nếu không bạn sẽ nhận được thông báo chung chung không hữu ích "Đã xảy ra lỗi". thông báo lỗi.
Bị trả lại

1
Có cách nào để thay đổi Tin nhắn không?
saquib adil

28

Ví dụ như thế này:

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

Điều này sẽ trả về một phản hồi như thế này (giả sử là JSON, nhưng cùng một nguyên tắc cơ bản cho 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."]

Tất nhiên, bạn có thể xây dựng đối tượng / danh sách lỗi của mình theo bất kỳ cách nào bạn muốn, ví dụ như thêm tên trường, id trường, v.v.

Ngay cả khi đó là lệnh gọi Ajax "một chiều" giống như POST của một thực thể mới, bạn vẫn nên trả lại một thứ gì đó cho người gọi - điều gì đó cho biết yêu cầu có thành công hay không. Hãy tưởng tượng một trang web mà người dùng của bạn sẽ thêm một số thông tin về họ thông qua yêu cầu AJAX POST. Điều gì sẽ xảy ra nếu thông tin họ đã cố gắng nhập không hợp lệ - làm thế nào họ biết được liệu hành động Lưu của họ có thành công hay không?

Cách tốt nhất để làm điều này là sử dụng Good Old HTTP Status Codes như 200 OKvà như vậy. Bằng cách đó, JavaScript của bạn có thể xử lý đúng các lỗi bằng cách sử dụng các lệnh gọi lại chính xác (lỗi, thành công, v.v.).

Đây là một hướng dẫn hay về phiên bản nâng cao hơn của phương pháp này, sử dụng ActionFilter và jQuery: http://asp.net/web-api/videos/getting-started/custom-validation


Điều đó chỉ trả về enquiryđối tượng của tôi , mặc dù nó không cho biết thuộc tính nào không hợp lệ? Vì vậy, Nếu tôi rời CustomerAccountNumbertrống rỗng, nó nên nói thông điệp xác nhận mặc định (trường CusomterAccountNumber là cần thiết ..)
CallumVass

Tôi hiểu rồi, vậy đây có phải là cách "chính xác" để xử lý Xác thực mô hình không? Có vẻ lộn xộn chút để tôi ..
CallumVass

Có nhiều cách khác để làm điều đó, chẳng hạn như kết nối với xác thực jQuery. Dưới đây là một ví dụ của Microsoft đẹp: asp.net/web-api/videos/getting-started/custom-validation
Anders Arpi

Phương thức này và phương thức được chọn làm câu trả lời "phải" giống hệt nhau về mặt chức năng, vì vậy câu trả lời này có giá trị bổ sung là cho bạn biết cách bạn có thể tự thực hiện mà không cần bộ lọc hành động.
Shaun Wilson

Tôi đã phải thay đổi dòng errors.Add(error.ErrorMessage);để errors.Add(error.Exception.Message);để làm việc này cho tôi.
Caltor

9

8

Hoặc, nếu bạn đang tìm kiếm bộ sưu tập lỗi đơn giản cho ứng dụng của mình .. đây là cách triển khai của tôi về điều này:

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

Phản hồi thông báo lỗi sẽ giống như sau:

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

5

Thêm mã bên dưới vào tệp 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

Tại đây bạn có thể kiểm tra để hiển thị từng lỗi trạng thái mô hình một

 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

Tôi đã gặp sự cố khi triển khai mẫu giải pháp được chấp nhận , nơi của tôi ModelStateFiltersẽ luôn trả về false(và sau đó là 400) actionContext.ModelState.IsValidcho các đối tượng mô hình nhất định:

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

Tôi chỉ chấp nhận JSON, vì vậy tôi đã triển khai một lớp chất kết dính mô hình tùy chỉnh:

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

Tôi đăng ký trực tiếp sau mô hình của mình qua

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.