Xây dựng Cơ chế Xác thực Dữ liệu Tùy chỉnh và Quản lý Phản hồi HTTP trong .NET Core

Thực hiện xác thực cấp độ lớp

Trong nhiều kịch bản nghiệp vụ, việc kiểm tra tính hợp lệ chỉ dựa trên từng thuộc tính đơn lẻ là chưa đủ. Chúng ta thường cần so sánh giá trị giữa hai hoặc nhiều trường khác nhau trong cùng một đối tượng để đảm bảo tính nhất quán. Cách tiếp cận phổ biến và linh hoạt nhất cho yêu cầu này là tạo ra một lớp kế thừa từ ValidationAttribute.

Dưới đây là ví dụ về một thuộc tính xác thực tùy chỉnh giúp đảm bảo tên sản phẩm không trùng lặp với phần tóm tắt:

using System;
using System.ComponentModel.DataAnnotations;

/// <summary>
/// Đảm bảo hai trường được chỉ định có giá trị khác nhau
/// </summary>
public class FieldsMustDifferAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        // Lấy instance của đối tượng đang được kiểm tra
        var targetObject = validationContext.ObjectInstance as InventoryItemDto;

        if (targetObject == null)
        {
            return ValidationResult.Success;
        }

        // Giả sử chúng ta muốn so sánh Value của trường hiện tại với một trường khác
        // Ở đây logic cụ thể phụ thuộc vào thiết kế DTO, nhưng cơ chế chung là truy xuất các property liên quan
        
        // Ví dụ giả định: Nếu Tên Hàng hóa giống mô tả ngắn thì lỗi
        if (!string.IsNullOrEmpty(targetObject.ItemName) && 
            !string.IsNullOrEmpty(targetObject.ShortDesc))
        {
            if (targetObject.ItemName.Equals(targetObject.ShortDesc))
            {
                return new ValidationResult(
                    "Tên hàng hóa phải khác biệt hoàn toàn so với mô tả ngắn",
                    new[] { nameof(targetObject.ItemName), nameof(targetObject.ShortDesc) }
                );
            }
        }

        return ValidationResult.Success;
    }
}

Áp dụng thuộc tính lên mô hình dữ liệu

Sau khi định nghĩa lớp xác thực, chúng ta sẽ gắn nó vào đối tượng truyền tải dữ liệu (DTO) tương ứng. Kết hợp cùng các thuộc tính chuẩn như [Required] hoặc [MaxLength] để tăng cường độ tin cậy đầu vào.

public abstract class InventoryItemDto
{
    [Required(ErrorMessage = "Vui lòng nhập tên sản phẩm")]
    [StringLength(255)]
    [Display(Name = "Tên Sản Phẩm")]
    public string ItemName { get; set; }

    [Required]
    [MaxLength(500)]
    [Display(Name = "Mô Tả Ngắn")]
    public string ShortDesc { get; set; }

    // Áp dụng rule tự定義 lên cặp trường này
    [FieldsMustDiffer]
    public object Placeholder { get; set; } 

    public decimal BasePrice { get; set; }
    public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
    public DateTime? LastModifiedDate { get; set; }
    
    // Các thông tin bổ trợ khác
    public ICollection<InventoryPhotoDto> MediaFiles { get; set; } 
    = new List<InventoryPhotoDto>();
}

Tùy chỉnh mã phản hồi lỗi Model State

Mặc định, ASP.NET Core trả về mã trạng thái HTTP 400 (Bad Request) khi dữ liệu gửi đến bị lỗi xác thực (ModelState.IsValid trả về false). Tuy nhiên, theo chuẩn RESTful tốt hơn, lỗi xác thực nên được biểu thị bằng mã 422 (Unprocessable Entity) để phân biệt rõ ràng giữa lỗi cú pháp request và lỗi logic dữ liệu.

Cấu hình này được thực hiện ngay trong quá trình khởi tạo dịch vụ controller tại file Program.cs:

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(apiOpts =>
    {
        // Thiết lập hàm factory để tạo phản hồi khi ModelState Invalid
        apiOpts.InvalidModelStateResponseFactory = requestContext =>
        {
            // Tạo đối tượng chi tiết vấn đề chuẩn RFC 7807
            var problemDetails = new Microsoft.AspNetCore.Mvc.ValidationProblemDetails(requestContext.ModelState)
            {
                Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
                Title = "Không thể xử lý dữ liệu gửi đi",
                Status = StatusCodes.Status422UnprocessableEntity, // Chuyển từ 400 sang 422
                Detail = "Vui lòng kiểm tra lại các thông báo lỗi chi tiết bên dưới",
                Instance = requestContext.HttpContext.Request.Path
            };

            // Thêm ID theo dõi truy vấn
            problemDetails.Extensions.Add("traceId", requestContext.HttpContext.TraceIdentifier);

            return new UnprocessableEntityObjectResult(problemDetails)
            {
                ContentTypes = { "text/plain", "application/problem+json" }
            };
        };
    });

Kiểm thử kết quả

Khi gửi một yêu cầu POST với dữ liệu vi phạm quy tắc (ví dụ: ItemName bằng ShortDesc), hệ thống sẽ không trả về 400 nữa mà trả về 422 kèm theo cấu trúc JSON chứa danh sách các lỗi cụ thể xảy ra tại từng trường, giúp client dễ dàng điều hướng người dùng sửa lỗi chính xác hơn.

Thẻ: .NET-Core web-api validation csharp http-status-codes

Đăng vào ngày 10 tháng 6 lúc 03:46