ASP.NET Core: Content Negotiation trong ASP.NET Web API
Bài viết này mô tả cách ASP.NET Web API triển khai Content Negotiation (đàm phán nội dung) cho ASP.NET 4.x.
Đặc tả HTTP (RFC 2616) định nghĩa đàm phán nội dung là "quá trình chọn cách trình bày tốt nhất cho một phản hồi nhất định khi có sẵn nhiều cách trình bày". Cơ chế chính để đàm phán nội dung trong HTTP là các tiêu đề yêu cầu sau:
- Chấp nhận: Loại phương tiện nào được chấp nhận cho phản hồi, chẳng hạn như "application/json," "application/xml" hoặc loại phương tiện tùy chỉnh như "application/vnd.example+xml"
- Bộ ký tự chấp nhận: Bộ ký tự nào được chấp nhận, chẳng hạn như UTF-8 hoặc ISO 8859-1.
- Mã hóa chấp nhận: Mã hóa nội dung nào được chấp nhận, chẳng hạn như gzip.
- Ngôn ngữ chấp nhận: Ngôn ngữ tự nhiên ưa thích, chẳng hạn như "en-us".
Máy chủ (Server) cũng có thể xem các phần khác của yêu cầu HTTP. Ví dụ: nếu yêu cầu chứa tiêu đề X-Requested-With, biểu thị yêu cầu AJAX, thì máy chủ có thể mặc định là JSON nếu không có header Accept.
Trong bài viết này, ta sẽ xem xét cách API Web sử dụng các header Accept và Accept-Charset. (Tại thời điểm này, không có hỗ trợ tích hợp nào cho Accept-Encoding hoặc Accept-Language.)
Tuần tự hóa (Serialization)
Nếu controller API Web trả về một tài nguyên dưới dạng loại CLR, thì quy trình sẽ tuần tự hóa giá trị trả về và ghi nó vào phần nội dung phản hồi HTTP.
Ví dụ: hãy xem xét hành động của controller sau:
public Product GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return item;
}
Một khách hàng có thể gửi yêu cầu HTTP này:
GET http://localhost.:21069/api/products/1 HTTP/1.1
Host: localhost.:21069
Accept: application/json, text/javascript, */*; q=0.01
Đáp lại, máy chủ có thể gửi:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 57
Connection: Close
{"Id":1,"Name":"Gizmo","Category":"Widgets","Price":1.99}
Trong ví dụ này, ứng dụng khách đã yêu cầu JSON, Javascript hoặc "bất kỳ thứ gì" (*/*). Máy chủ phản hồi bằng cách thể hiện JSON của đối tượng Product
. Lưu ý rằng header Content-Type trong phản hồi được đặt thành "application/json".
Controller cũng có thể trả về một đối tượng HttpResponseMessage. Để chỉ định đối tượng CLR cho nội dung phản hồi, hãy gọi phương thức tiện ích mở rộng CreateResponse:
public HttpResponseMessage GetProduct(int id)
{
var item = _products.FirstOrDefault(p => p.ID == id);
if (item == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return Request.CreateResponse(HttpStatusCode.OK, item);
}
Tùy chọn này cho phép bạn kiểm soát nhiều hơn các chi tiết của phản hồi. Bạn có thể đặt mã trạng thái, thêm tiêu đề HTTP, v.v.
Đối tượng tuần tự hóa tài nguyên được gọi là trình định dạng phương tiện (media formatter). Các trình định dạng phương tiện dẫn xuất từ lớp MediaTypeFormatter. API Web cung cấp các trình định dạng phương tiện cho XML và JSON, đồng thời bạn có thể tạo các trình định dạng tùy chỉnh để hỗ trợ các loại phương tiện khác.
Cách đàm phán nội dung hoạt động
Đầu tiên, quy trình nhận dịch vụ IContentNegotiator từ đối tượng HttpConfiguration. Nó cũng lấy danh sách các trình định dạng phương tiện từ collection HttpConfiguration.Formatters.
Tiếp theo, quy trình gọi IContentNegotiator.Negotiate, truyền vào:
- Loại đối tượng cần tuần tự hóa
- Collection các trình định dạng phương tiện
- Yêu cầu HTTP
Phương thức Negotiate trả về hai thông tin:
- Sử dụng định dạng nào
- Loại phương tiện cho phản hồi
Nếu không tìm thấy trình định dạng nào, phương thức Negotiate sẽ trả về giá trị null và máy khách nhận được lỗi HTTP 406 (Không được chấp nhận).
Đoạn mã sau đây cho thấy cách bộ điều khiển có thể trực tiếp gọi đàm phán nội dung:
public HttpResponseMessage GetProduct(int id)
{
var product = new Product()
{ Id = id, Name = "Gizmo", Category = "Widgets", Price = 1.99M };
IContentNegotiator negotiator = this.Configuration.Services.GetContentNegotiator();
ContentNegotiationResult result = negotiator.Negotiate(
typeof(Product), this.Request, this.Configuration.Formatters);
if (result == null)
{
var response = new HttpResponseMessage(HttpStatusCode.NotAcceptable);
throw new HttpResponseException(response));
}
return new HttpResponseMessage()
{
Content = new ObjectContent<Product>(
product, // What we are serializing
result.Formatter, // The media formatter
result.MediaType.MediaType // The MIME type
)
};
}
Đoạn code trên này tương đương với những gì pipeline thực hiện tự động.
Trình đàm phán nội dung mặc định
Lớp DefaultContentNegotiator cung cấp cách triển khai mặc định của IContentNegotiator. Nó sử dụng một số tiêu chí để chọn một bộ định dạng.
Đầu tiên, bộ định dạng phải có khả năng tuần tự hóa kiểu. Điều này được xác minh bằng cách gọi MediaTypeFormatter.CanWriteType.
Tiếp theo, trình đàm phán nội dung sẽ xem xét từng trình định dạng và đánh giá mức độ phù hợp của nó với yêu cầu HTTP. Để đánh giá sự phù hợp, trình đàm phán nội dung xem xét hai điều trên bộ định dạng:
- Collection SupportedMediaTypes chứa danh sách các loại phương tiện được hỗ trợ. Trình đàm phán nội dung cố gắng khớp danh sách này với header Accept yêu cầu. Lưu ý rằng header Accept có thể bao gồm các phạm vi. Ví dụ: "text/plain" khớp với text/* hoặc */*.
- Collection MediaTypeMappings chứa danh sách các đối tượng MediaTypeMapping. Lớp MediaTypeMapping cung cấp một cách chung để khớp các yêu cầu HTTP với các loại phương tiện. Ví dụ: nó có thể ánh xạ header HTTP tùy chỉnh tới một loại phương tiện cụ thể.
Nếu có nhiều sự tương thích thì sự tương thích nào có hệ số chất lượng cao nhất sẽ thắng. Ví dụ:
Accept: application/json, application/xml; q=0.9, */*; q=0.1
Trong ví dụ này, application/json có hệ số chất lượng ngụ ý là 1.0, vì vậy nó được ưu tiên hơn application/xml.
Nếu không tìm thấy kết quả phù hợp, trình đàm phán nội dung sẽ cố gắng so khớp loại phương tiện của nội dung yêu cầu, nếu có. Ví dụ: nếu yêu cầu chứa dữ liệu JSON, thì trình đàm phán nội dung sẽ tìm trình định dạng JSON.
Nếu vẫn không có kết quả trùng khớp, trình đàm phán nội dung chỉ cần chọn trình định dạng đầu tiên có thể tuần tự hóa kiểu.
Chọn mã hóa ký tự
Sau khi chọn bộ định dạng, trình đàm phán nội dung sẽ chọn cách mã hóa ký tự tốt nhất bằng cách xem property SupportedEncodings trên bộ định dạng và khớp nó với header Accept-Charset trong yêu cầu (nếu có).