Đang mở bài viết gốc.
Đang mở bài viết gốc…
Bạn sẽ được chuyển đến nguồn trong giây lát.
Một trong những khía cạnh thường bị bỏ qua nhất khi xây dựng API là cách thức truyền tải lỗi. Một API được thiết kế tốt sẽ thông báo cho người dùng một cách hiệu quả khi có sự cố xảy ra. Các phản hồi lỗi nhất quán không chỉ là phương pháp thực hành tốt nhất mà còn là một sự cần thiết. Chúng giúp các lập trình viên khắc phục sự cố nhanh hơn, mang lại trải nghiệm tốt hơn cho nhà phát triển và tạo nền tảng tin cậy cho API của bạn. Bằng cách áp dụng các phản hồi lỗi tiêu chuẩn, bạn làm cho API của mình trở nên dễ dự đoán, chuyên nghiệp và thân thiện với người dùng. Trong bài viết này, chúng ta sẽ tìm hiểu cách tiêu chuẩn Problem Details có thể giúp chúng ta cấu trúc các phản hồi lỗi trong ASP.NET Core Web APIs và giúp khách hàng dễ dàng hiểu cũng như xử lý các lỗi API hơn.
Mục lục
Problem Details là gì?
Tiêu chuẩn “Problem Details for HTTP APIs” lần đầu tiên được giới thiệu bởi RFC 7807 và sau đó được tinh chỉnh gần đây trong RFC 9457. Nó xác định một tập hợp các quy tắc để thể hiện chi tiết lỗi ở định dạng JSON (và XML) có cấu trúc, nhất quán và máy có thể đọc được. Mục tiêu của tiêu chuẩn này là làm cho phản hồi lỗi trở nên giàu thông tin và có thể thực thi được, không chỉ đối với các lập trình viên con người mà còn đối với các hệ thống tiêu thụ API trong thời gian chạy.

Cấu trúc đối tượng Problem Details chứa các trường sau.
type: Một tham chiếu URI xác định loại vấn đề. Nó nhằm mục đích cung cấp cho người vận hành nơi để tìm thêm thông tin về lỗi. Nếu không có hoặc không áp dụng, nó được giả định là “about:blank”.
title: Một bản tóm tắt ngắn gọn, dễ đọc về loại vấn đề. Nó không nên thay đổi giữa các lần xuất hiện của vấn đề, ngoại trừ mục đích bản địa hóa.
status: Mã trạng thái HTTP do máy chủ gốc tạo ra cho lần xuất hiện vấn đề này.
detail: Một lời giải thích dễ đọc dành riêng cho lần xuất hiện vấn đề này. Không giống như tiêu đề, nội dung của trường này có thể thay đổi tùy theo từng lần xuất hiện.
instance: Một tham chiếu URI xác định lần xuất hiện cụ thể của vấn đề. Nó có thể hoặc không mang lại thông tin thêm nếu được tham chiếu.
Lợi ích của việc sử dụng Problem Details
Dưới đây là một số lợi ích của việc sử dụng Problem Details.
Cải thiện trải nghiệm nhà phát triển: Nó nâng cao trải nghiệm của nhà phát triển bằng cách cung cấp thông tin rõ ràng, chuẩn hóa về các vấn đề, giúp API dễ dàng tích hợp và bảo trì hơn.
Cải thiện khả năng gỡ lỗi: Cung cấp thông tin chi tiết, có cấu trúc về các lỗi, đơn giản hóa việc khắc phục sự cố cho cả nhà phát triển API và người tiêu dùng.
Giảm thời gian và chi phí phát triển: Chuẩn hóa các phản hồi lỗi trên các API, giúp các nhà phát triển dự đoán và xử lý lỗi nhanh hơn. Các nhà phát triển dành quá nhiều thời gian để giải mã lỗi và triển khai các trình xử lý tùy chỉnh cho từng API, điều này làm giảm chi phí tổng thể liên quan đến việc tiếp nhận API cũng như bảo trì liên tục.
Trải nghiệm khách hàng tốt hơn: Cho phép các ứng dụng khách diễn giải và phản hồi lỗi theo chương trình với sự mơ hồ tối thiểu.
Khả năng mở rộng: Có thể tùy chỉnh để bao gồm các trường bổ sung như mã lỗi hoặc ID theo dõi để có thêm ngữ cảnh.
Tuân thủ các tiêu chuẩn: Căn chỉnh các API với các phương pháp thực hành tốt nhất đã được thiết lập (RFC 9457), thúc đẩy khả năng tương tác và uy tín.
ĐỌC THÊM: Bắt đầu với GraphQL trong ASP.NET Core
Cấu hình Problem Details trong ASP.NET Core
Hãy tạo một dự án ASP.NET Core Web API với controller và phương thức hành động sau đây, vốn đang ném ra một ngoại lệ chưa được xử lý giả định.
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
public IActionResult Get()
{
throw new Exception("Product Not Found.");
}
}Nếu bạn chạy dự án, bạn sẽ thấy trang ngoại lệ mặc định sau đây.

ASP.NET Core có hỗ trợ tích hợp cho Problem Details, có thể được kích hoạt trong tệp Program.cs bằng cách gọi phương thức AddProblemDetails. Phương thức này đăng ký middleware chi tiết vấn đề sẽ xử lý các ngoại lệ và trả về phản hồi chi tiết vấn đề.
builder.Services.AddProblemDetails();Chúng ta cũng cần kích hoạt một middleware khác bằng phương thức UseExceptionHandler, phương thức này tự động chuyển đổi các ngoại lệ chưa được xử lý thành các phản hồi Problem Details.
app.UseExceptionHandler();Bây giờ hãy chạy lại cùng một API và lần này bạn sẽ nhận thấy rằng ngoại lệ chưa được xử lý được tự động dịch sang phản hồi Problem Details:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500,
"traceId": "00-3d4076ecea61224816c7f595c90d2897-7514c47216448cda-00"
}Cuối cùng, bạn có thể gọi phương thức UseStatusCodePages để thêm một middleware sẽ trả về phản hồi chi tiết vấn đề cho các mã trạng thái HTTP phổ biến.
app.UseStatusCodePages();Bây giờ hãy thử truy cập bất kỳ Web API nào không tồn tại bằng cách nhập một URL ngẫu nhiên và bạn sẽ thấy phản hồi Problem Details sau đây được tạo tự động.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404
}Xử lý toàn cục Problem Details bằng IExceptionHandler
Nếu chúng ta có một ứng dụng lớn, thì việc xử lý tất cả các ngoại lệ chưa được xử lý trên toàn cục tại một nơi là một phương pháp tốt. Trình xử lý ngoại lệ toàn cục không chỉ nên bắt tất cả các ngoại lệ chưa được xử lý mà còn nên ánh xạ từng ngoại lệ tới phản hồi chi tiết vấn đề chính xác trước khi trả về. Một giao diện IExceptionHandler mới được giới thiệu trong .NET 8 giúp việc triển khai trình xử lý ngoại lệ toàn cục như vậy trở nên rất dễ dàng.
public class CustomExceptionHandler : IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext httpContext,
Exception exception, CancellationToken cancellationToken)
{
var statusCode = exception switch
Trình xử lý ngoại lệ tùy chỉnh ở trên đã xác định phương thức TryHandleAsync sẽ được gọi bởi middleware problem details khi có bất kỳ ngoại lệ nào được ném ra.
ĐỌC THÊM: Distributed Caching in ASP.NET Core using Redis Cache
Tiếp theo, chúng ta đang ánh xạ các ngoại lệ khác nhau như BadHttpRequestException và UnauthorizedAccessException với các mã trạng thái HTTP thích hợp bằng cách sử dụng cú pháp switch statement mới của C#. Chúng ta có thể ánh xạ các ngoại lệ cụ thể hơn theo cùng một cách, nếu không, chúng ta có thể ánh xạ tất cả các ngoại lệ còn lại với mã trạng thái 500 – Internal server error như chúng ta đã làm trong trình xử lý của mình.
var statusCode = exception switch
{
BadHttpRequestException => StatusCodes.Status400BadRequest,
UnauthorizedAccessException => StatusCodes.Status401Unauthorized,
_ => StatusCodes.Status500InternalServerError
};Cuối cùng, chúng ta đã tạo một đối tượng ProblemDetails() với tiêu đề, mã trạng thái và chi tiết ngoại lệ, sau đó trả về đối tượng đó bằng cách ghi vào phản hồi.
var problemDetails = new ProblemDetails
{
Title = statusCode == StatusCodes.Status500InternalServerError
? "Internal Server Error"
: "A handled exception occurred",
Status = statusCode,
Type = exception?.GetType().Name,
Detail = exception?.Message,
Instance = httpContext.Request.Path
};Lưu ý rằng chúng ta trả về true ở cuối phương thức, điều này có nghĩa là chúng ta đã xử lý ngoại lệ và pipeline yêu cầu có thể dừng lại tại đây.
Để đăng ký trình xử lý ngoại lệ, tất cả những gì bạn cần làm là gọi phương thức AddExceptionHandler() trong tệp Program.cs của bạn.
builder.Services.AddExceptionHandler<CustomExceptionHandler>();Chạy lại ứng dụng một lần nữa và lần này bạn sẽ thấy phản hồi problem details tương tự như sau.
{
"type": "Exception",
"title": "Internal Server Error",
"status": 500,
"detail": "Product Not Found.",
"instance": "/products"
}Tùy chỉnh phản hồi Problem Details trong ASP.NET Core
Bạn có thể sử dụng IExceptionHandler tùy chỉnh mà chúng ta đã triển khai ở trên để tùy chỉnh các phản hồi Problem Details được tạo ra. Ví dụ: nếu bạn muốn thêm các chi tiết bổ sung như requestId hoặc traceId vào phản hồi problem details, bạn có thể thêm thông tin này vào thuộc tính từ điển Extensions có sẵn trong đối tượng ProblemDetails như sau.
var problemDetails = new ProblemDetails
{
Title = statusCode == StatusCodes.Status500InternalServerError
? "Internal Server Error"
: "A handled exception occurred",
Status = statusCode,
Type = exception?.GetType().Name,
Detail = exception?.Message,
Instance = httpContext.Request.Path
};
problemDetails.Extensions.Add("requestId", httpContext.TraceIdentifier);
Activity? activity = httpContext.Features.Get<IHttpActivityFeature>()?.Activity;
problemDetails.Extensions.TryAdd("traceId", activity?.Id);Chạy lại ứng dụng sẽ hiển thị cho bạn phản hồi problem details tương tự như kết quả sau.
{
"type": "Exception",
"title": "Internal Server Error",
"status": 500,
"detail": "Product Not Found.",
"instance": "/products",
"requestId": "0HN8TUOBKNMS1:00000001",
"traceId": "00-d89d66409431f7af8aa9a7d994a3d5e9-8849ae6e668967dc-00"
}Nếu bạn không sử dụng trình xử lý ngoại lệ toàn cục, thì problem details có thể được tùy chỉnh trong tệp Program.cs bằng cách sử dụng thuộc tính CustomizeProblemDetails có sẵn trong lớp ProblemDetailsOptions. Những tùy chỉnh này sẽ được áp dụng cho tất cả các problem details được tự động tạo ra.
builder.Services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = context =>
{
context.ProblemDetails.Instance =
$"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}";
context.ProblemDetails.Extensions.TryAdd("requestId", context.HttpContext.TraceIdentifier);
Activity? activity = context.HttpContext.Features.Get<IHttpActivityFeature>()?.Activity;
context.ProblemDetails.Extensions.TryAdd("traceId", activity?.Id);
};
});Kết quả sau đây sẽ được tạo ra với các thay đổi mã ở trên.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.6.1",
"title": "An error occurred while processing your request.",
"status": 500,
"instance": "GET /products",
"traceId": "00-85fc42f1c7f131ada01dd3a862fbc0c6-5abd388fe39477e7-00",
"requestId": "0HN8TUSUDTVN2:00000001"
}Trả về Problem Details từ Web APIs
Nếu endpoint Web API của bạn cần cung cấp thêm chi tiết lỗi cho máy khách theo cách tuân thủ các phương pháp hay nhất về xử lý lỗi REST API hiện đại, thì bạn có thể xây dựng đối tượng ProblemDetails với thông tin thích hợp và chuyển nó vào các phương thức trợ giúp tích hợp sẵn của ASP.NET Core như NotFound hoặc BadRequest.
public IActionResult Get()
{
return NotFound(new ProblemDetails
{
Title = "Product Not Found",
Status = StatusCodes.Status404NotFound,
Detail = "The requested product could not be found."
});
}Gọi endpoint trên sẽ tạo ra kết quả sau trong trình duyệt.
{
"title": "Product Not Found",
"status": 404,
"detail": "The requested product could not be found."
}ASP.NET Core cũng có một phương thức trợ giúp tích hợp sẵn gọi là Problem, có thể được sử dụng để bao bọc thông tin lỗi vào một đối tượng ProblemDetails và trả về dưới dạng phản hồi. Loại nội dung của phản hồi sẽ được đặt thành "application/problem+json", đây là loại phương tiện tiêu chuẩn cho các phản hồi Problem Details.
public IActionResult Get()
{
return Problem(
type: "Bad Request",
title: "Invalid request",
detail: "The provided request parameters are invalid.",
statusCode: StatusCodes.Status400BadRequest);
}Gọi endpoint trên sẽ tạo ra kết quả sau trong trình duyệt.
{
"type": "Bad Request",
"title": "Invalid request",
"status": 400,
"detail": "The provided request parameters are invalid.",
"instance": "GET /products",
"traceId": "00-0cdea3af3aa333ad4748f2b4d39823cb-821cfbe165d141c6-00",
"requestId": "0HN92JDDE6O87:00000001"
}Kết luận
Problem Details đóng vai trò là một cơ chế quan trọng để giao tiếp lỗi API hiệu quả. Bằng cách giải quyết các anti-pattern phổ biến đã gây khó khăn cho các HTTP API từ lâu, tiêu chuẩn này mở đường cho một hệ sinh thái API đáng