ASP.NET Core: Model Validation (Xác thực model) trong ASP.NET Web API
Bài viết này trình bày cách chú giải (annotate) model của bạn, sử dụng chú giải để xác thực dữ liệu (data validate) và xử lý các lỗi xác thực trong API web của bạn. Khi client gửi dữ liệu tới API web của bạn, bạn thường muốn xác thực dữ liệu trước khi thực hiện bất kỳ quá trình xử lý nào.
Chú giải dữ liệu
Trong ASP.NET Web API, bạn có thể sử dụng các attribute từ namespace System.ComponentModel.DataAnnotations để đặt quy tắc xác thực cho các property trên model của mình. Hãy xem xét model sau:
using System.ComponentModel.DataAnnotations;
namespace MyApi.Models
{
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public decimal Price { get; set; }
[Range(0, 999)]
public double Weight { get; set; }
}
}
Nếu bạn đã sử dụng xác thực model trong ASP.NET MVC, điều này sẽ trông quen thuộc. Attribute Required cho biết property Name
không được rỗng. Attribute Range cho biết Weight
phải nằm trong khoảng từ 0 đến 999.
Giả sử rằng một client gửi yêu cầu POST với biểu diễn JSON sau:
{ "Id":4, "Price":2.99, "Weight":5 }
Bạn có thể thấy rằng client không bao gồm property Name
được đánh dấu là bắt buộc. Khi API Web chuyển đổi JSON thành một thể hiện Product
, nó sẽ xác thực Product
dựa trên các attribute xác thực. Trong action controller của bạn, bạn có thể kiểm tra xem model có hợp lệ hay không:
using MyApi.Models;
using System.Net;
using System.Net.Http;
using System.Web.Http;
namespace MyApi.Controllers
{
public class ProductsController : ApiController
{
public HttpResponseMessage Post(Product product)
{
if (ModelState.IsValid)
{
// Do something with the product (not shown).
return new HttpResponseMessage(HttpStatusCode.OK);
}
else
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
}
}
}
Xác thực model không đảm bảo rằng dữ liệu khách hàng được an toàn. Có thể cần xác nhận bổ sung ở các lớp khác của ứng dụng. (Ví dụ: lớp dữ liệu có thể thực thi các ràng buộc khóa ngoại.) Hướng dẫn Sử dụng API Web với Entity Framework khám phá một số vấn đề này.
"Under-Posting": Under-Posting xảy ra khi client bỏ qua một số property. Ví dụ: giả sử client gửi như sau:
{"Id":4, "Name":"Gizmo"}
Ở đây, khách hàng không chỉ định giá trị cho Price
hoặc Weight
. Trình định dạng JSON gán giá trị mặc định bằng 0 cho các property bị thiếu.
Trạng thái model là hợp lệ vì số 0 là giá trị hợp lệ cho các property này. Cho dù đây là một vấn đề phụ thuộc vào kịch bản của bạn. Ví dụ: trong thao tác cập nhật, bạn có thể muốn phân biệt giữa "zero" và "not set". Để buộc client phải thiết lập giá trị, hãy đặt property thành dạng nullable và đặt attribute Required như sau:
[Required]
public decimal? Price { get; set; }
"Over-Posting": Client cũng có thể gửi nhiều dữ liệu hơn bạn mong đợi. Ví dụ:
{"Id":4, "Name":"Gizmo", "Color":"Blue"}
Ở đây, JSON bao gồm một property ("Color") không tồn tại trong model Product
. Trong trường hợp này, trình định dạng JSON chỉ cần bỏ qua giá trị này. (Trình định dạng XML cũng làm như vậy.) Over-Posting sẽ gây ra sự cố nếu model của bạn có các property mà bạn dự định ở chế độ chỉ đọc. Ví dụ:
public class UserProfile
{
public string Name { get; set; }
public Uri Blog { get; set; }
public bool IsAdmin { get; set; } // uh-oh!
}
Bạn không muốn người dùng cập nhật property IsAdmin
và tự nâng mình lên thành quản trị viên! Chiến lược an toàn nhất là sử dụng lớp model khớp chính xác với những gì client được phép gửi:
public class UserProfileDTO
{
public string Name { get; set; }
public Uri Blog { get; set; }
// Leave out "IsAdmin"
}
Ghi chú
Bài đăng trên blog của Brad Wilson "Xác thực đầu vào so với Xác thực model trong ASP.NET MVC" có một cuộc thảo luận hay về Under-Posting và Over-Posting. Mặc dù bài viết nói về ASP.NET MVC 2 nhưng các vấn đề vẫn liên quan đến API Web.
Xử lý lỗi xác thực
API Web không tự động trả về lỗi cho máy khách khi xác thực không thành công. Việc kiểm tra trạng thái model và phản hồi thích hợp là tùy thuộc vào hành động của controller.
Bạn cũng có thể tạo bộ lọc action để kiểm tra trạng thái model trước khi gọi action của controller. Đoạn code sau đây cho thấy một ví dụ:
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using System.Web.Http.ModelBinding;
namespace MyApi.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid == false)
{
actionContext.Response = actionContext.Request.CreateErrorResponse(
HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
}
}
Nếu xác thực model không thành công, bộ lọc này sẽ trả về phản hồi HTTP chứa lỗi xác thực. Trong trường hợp đó, action của controller không được gọi.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Tue, 16 Jul 2013 21:02:29 GMT
Content-Length: 331
{
"Message": "The request is invalid.",
"ModelState": {
"product": [
"Required property 'Name' not found in JSON. Path '', line 1, position 17."
],
"product.Name": [
"The Name field is required."
],
"product.Weight": [
"The field Weight must be between 0 and 999."
]
}
}
Để áp dụng bộ lọc này cho tất cả controller API Web, hãy thêm một thể hiện của bộ lọc vào collection HttpConfiguration.Filters trong khi định cấu hình:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new ValidateModelAttribute());
// ...
}
}
Một tùy chọn khác là đặt bộ lọc làm attribute trên các controller hoặc action của controller riêng lẻ:
public class ProductsController : ApiController
{
[ValidateModel]
public HttpResponseMessage Post(Product product)
{
// ...
}
}