Định dạng dữ liệu response trong ASP.NET Core Web API
ASP.NET Core MVC hỗ trợ định dạng dữ liệu response (phản hồi) theo các định dạng được chỉ định hoặc theo yêu cầu của client (máy khách).
Kết quả action theo định dạng cụ thể (Format-specific Action Results)
Một số kiểu kết quả action gắn với định dạng cụ thể, chẳng hạn JsonResult và ContentResult. Action có thể trả về kết quả luôn dùng một định dạng nhất định, bỏ qua yêu cầu định dạng khác từ client. Ví dụ, trả về JsonResult luôn cho dữ liệu JSON, còn ContentResult cho dữ liệu chuỗi văn bản thuần túy (plain text).
Action không bắt buộc phải trả về kiểu cụ thể nào. ASP.NET Core hỗ trợ mọi kiểu trả về. Kết quả từ action trả về các đối tượng không phải IActionResult sẽ được tuần tự hóa bằng implementation IOutputFormatter phù hợp.
Theo mặc định, phương thức tiện lợi ControllerBase.Ok trả về dữ liệu định dạng JSON:
[HttpGet]
public IActionResult Get()
=> Ok(_todoItemStore.GetList());Để trả về dữ liệu văn bản thuần túy, dùng ContentResult và phương thức tiện lợi Content:
[HttpGet("Version")]
public ContentResult GetVersion()
=> Content("v1.0.0");Với các action có nhiều kiểu trả về, hãy dùng IActionResult.
Thương lượng nội dung (Content negotiation)
Thương lượng nội dung xảy ra khi client chỉ định Accept header (tiêu đề Accept). Định dạng mặc định của ASP.NET Core là JSON. Thương lượng nội dung được:
- Implement bởi
ObjectResult. - Tích hợp trong các kết quả action theo mã trạng thái trả về từ các phương thức tiện lợi (đều dựa trên
ObjectResult).
Khi trả về kiểu model (mô hình), kiểu trả về là ObjectResult.
Ví dụ action dùng phương thức tiện lợi Ok và NotFound:
[HttpGet("{id:long}")]
public IActionResult GetById(long id)
{
var todo = _todoItemStore.GetById(id);
if (todo is null)
{
return NotFound();
}
return Ok(todo);
}Mặc định ASP.NET Core hỗ trợ các media type (loại phương tiện) sau:
application/jsontext/jsontext/plain
Các công cụ như Fiddler hoặc http-repl có thể đặt Accept request header để chỉ định định dạng trả về. Khi Accept header chứa kiểu mà server hỗ trợ, kiểu đó sẽ được trả về.
Controller action có thể trả về POCO (Plain Old CLR Object - đối tượng CLR thông thường). Khi trả về POCO, runtime tự động tạo ObjectResult bao bọc đối tượng. Client nhận đối tượng được tuần tự hóa và định dạng. Nếu đối tượng là null, response 204 No Content được trả về.
[HttpGet("{id:long}")]
public TodoItem? GetById(long id)
=> _todoItemStore.GetById(id);Accept header
Khi request chứa accept header, ASP.NET Core:
- Duyệt qua các media type trong accept header theo thứ tự ưu tiên.
- Tìm formatter có thể tạo response theo một trong các định dạng được chỉ định.
Nếu không tìm được formatter phù hợp:
- Trả về
406 Not AcceptablenếuMvcOptions.ReturnHttpNotAcceptableđược đặt làtrue, hoặc - Tìm formatter đầu tiên có thể tạo response.
Nếu không có Accept header trong request, formatter đầu tiên có thể xử lý đối tượng sẽ được dùng để tuần tự hóa response (không có thương lượng nào xảy ra).
Nếu Accept header chứa */*, Header bị bỏ qua trừ khi RespectBrowserAcceptHeader được đặt là true trong MvcOptions.
Trình duyệt và thương lượng nội dung
Không giống các API client thông thường, trình duyệt web cung cấp Accept header với nhiều định dạng kể cả ký tự đại diện. Mặc định, khi framework phát hiện request đến từ trình duyệt:
Acceptheader bị bỏ qua.- Nội dung được trả về bằng formatter output đầu tiên đã đăng ký có thể xử lý kiểu response.
Để cấu hình ứng dụng tôn trọng accept header của trình duyệt, đặt thuộc tính RespectBrowserAcceptHeader là true:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
});Cấu hình Formatter (bộ định dạng)
Ứng dụng cần hỗ trợ thêm định dạng có thể thêm NuGet package phù hợp và cấu hình. Có hai loại formatter riêng biệt: input formatter (dùng cho Model Binding) và output formatter (dùng để định dạng response).
Thêm hỗ trợ định dạng XML
Để cấu hình XML formatter dùng XmlSerializer, gọi AddXmlSerializerFormatters:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddXmlSerializerFormatters();Với đoạn code trên, các phương thức controller trả về định dạng phù hợp dựa trên Accept header của request.
Cấu hình formatter dựa trên System.Text.Json
Để cấu hình tính năng cho formatter System.Text.Json, dùng Microsoft.AspNetCore.Mvc.JsonOptions.JsonSerializerOptions. Ví dụ sau cấu hình PascalCase thay vì camelCase mặc định:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});Để cấu hình tùy chọn tuần tự hóa output cho action cụ thể, dùng JsonResult:
[HttpGet]
public IActionResult Get()
=> new JsonResult(
_todoItemStore.GetList(),
new JsonSerializerOptions { PropertyNamingPolicy = null });Thêm hỗ trợ JSON dựa trên Newtonsoft.Json
Mặc định formatter JSON dùng System.Text.Json. Để dùng formatter dựa trên Newtonsoft.Json, cài đặt NuGet package Microsoft.AspNetCore.Mvc.NewtonsoftJson và cấu hình trong Program.cs:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddNewtonsoftJson();Lệnh gọi AddNewtonsoftJson cấu hình các tính năng sau để dùng Newtonsoft.Json:
- Input và output formatter đọc/ghi JSON
JsonResult- JSON Patch
IJsonHelperTempData
Tiếp tục dùng formatter Newtonsoft.Json khi ứng dụng:
- Dùng thuộc tính
Newtonsoft.Jsonnhư[JsonProperty]hoặc[JsonIgnore]. - Tùy chỉnh cài đặt tuần tự hóa.
- Phụ thuộc vào tính năng của
Newtonsoft.Json.
Để cấu hình tính năng cho formatter Newtonsoft.Json, dùng SerializerSettings:
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});Để cấu hình tùy chọn tuần tự hóa output cho action cụ thể:
[HttpGet]
public IActionResult GetNewtonsoftJson()
=> new JsonResult(
_todoItemStore.GetList(),
new JsonSerializerSettings { ContractResolver = new DefaultContractResolver() });Định dạng response ProblemDetails và ValidationProblemDetails
Action method sau gọi ControllerBase.Problem để tạo response ProblemDetails:
[HttpGet("Error")]
public IActionResult GetError()
=> Problem("Something went wrong.");Response ProblemDetails luôn dùng camelCase, kể cả khi ứng dụng đặt định dạng PascalCase. ProblemDetails tuân theo RFC 7807, quy định chữ thường.
Khi thuộc tính [ApiController] được áp dụng cho lớp controller, controller tạo response ValidationProblemDetails khi Model Validation (xác thực mô hình) thất bại. Response này bao gồm dictionary dùng tên thuộc tính của model làm khóa lỗi.
Để định dạng tên thuộc tính dùng làm khóa lỗi, thêm implementation IMetadataDetailsProvider vào collection MvcOptions.ModelMetadataDetailsProviders. Ví dụ sau thêm SystemTextJsonValidationMetadataProvider dựa trên System.Text.Json, định dạng tên thuộc tính theo camelCase mặc định:
builder.Services.AddControllers();
builder.Services.Configure<MvcOptions>(options =>
{
options.ModelMetadataDetailsProviders.Add(
new SystemTextJsonValidationMetadataProvider());
});Để định dạng response ValidationProblemDetails dùng Newtonsoft.Json:
builder.Services.AddControllers()
.AddNewtonsoftJson();
builder.Services.Configure<MvcOptions>(options =>
{
options.ModelMetadataDetailsProviders.Add(
new NewtonsoftJsonValidationMetadataProvider());
});Chỉ định định dạng (Specify a format)
Để giới hạn định dạng response, áp dụng bộ lọc [Produces]. Giống hầu hết Filters (bộ lọc), [Produces] có thể áp dụng ở phạm vi action, controller, hoặc toàn cục:
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class TodoItemsController : ControllerBaseBộ lọc [Produces] trên:
- Buộc tất cả action trong controller trả về response JSON cho POCO và
ObjectResultcùng các kiểu dẫn xuất. - Trả về response JSON ngay cả khi formatter khác được cấu hình và client chỉ định định dạng khác.
Formatter trường hợp đặc biệt (Special case formatters)
Một số trường hợp đặc biệt được implement bằng formatter tích hợp. Mặc định, kiểu trả về string được định dạng là text/plain (text/html nếu được yêu cầu qua Accept header). Hành vi này có thể xóa bằng cách loại bỏ StringOutputFormatter.
Action có kiểu trả về là đối tượng model sẽ trả về 204 No Content khi trả về null. Hành vi này có thể xóa bằng cách loại bỏ HttpNoContentOutputFormatter.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
// using Microsoft.AspNetCore.Mvc.Formatters;
options.OutputFormatters.RemoveType<StringOutputFormatter>();
options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});Không có StringOutputFormatter: formatter JSON tích hợp định dạng kiểu trả về string. Nếu formatter JSON cũng bị loại bỏ và có formatter XML, formatter XML sẽ định dạng. Nếu không, kiểu trả về string trả về 406 Not Acceptable.
Không có HttpNoContentOutputFormatter: các đối tượng null được định dạng bằng formatter được cấu hình.
Ánh xạ URL định dạng response (Response format URL mappings)
Client có thể yêu cầu định dạng cụ thể như một phần của URL (trong query string, path, hoặc dùng phần mở rộng tệp như .xml hoặc .json).
[ApiController]
[Route("api/[controller]")]
[FormatFilter]
public class TodoItemsController : ControllerBase
{
private readonly TodoItemStore _todoItemStore;
public TodoItemsController(TodoItemStore todoItemStore)
=> _todoItemStore = todoItemStore;
[HttpGet("{id:long}.{format?}")]
public TodoItem? GetById(long id)
=> _todoItemStore.GetById(id);Route trên cho phép chỉ định định dạng bằng phần mở rộng tệp tùy chọn. Thuộc tính [FormatFilter] kiểm tra sự tồn tại của giá trị format trong RouteData và ánh xạ định dạng response sang formatter phù hợp.
| Route | Formatter |
|---|---|
/api/todoitems/5 | Formatter output mặc định |
/api/todoitems/5.json | Formatter JSON (nếu được cấu hình) |
/api/todoitems/5.xml | Formatter XML (nếu được cấu hình) |
Giải tuần tự hóa đa hình (Polymorphic deserialization)
Tính năng tích hợp hỗ trợ một phạm vi hạn chế của tuần tự hóa đa hình (polymorphic serialization) nhưng không hỗ trợ giải tuần tự hóa (deserialization). Giải tuần tự hóa đòi hỏi custom converter (bộ chuyển đổi tùy chỉnh).