Nguon: Microsoft Learn · .NET 8.0

Tạo Web API với ASP.NET Core

Nguồn: Create web APIs with ASP.NET Core

ASP.NET Core hỗ trợ tạo web API bằng controller hoặc sử dụng Minimal APIs (API tối giản). Controller trong web API là các lớp kế thừa từ ControllerBase. Controller được khởi tạo và hủy bỏ theo từng request.

Bài viết này cho thấy cách sử dụng controller để xử lý các yêu cầu web API. Để biết thông tin về tạo web API không dùng controller, xem Hướng dẫn: Tạo Minimal API với ASP.NET Core.

Lớp ControllerBase

Một web API dựa trên controller bao gồm một hoặc nhiều lớp controller kế thừa từ ControllerBase. Template dự án web API cung cấp một controller khởi đầu:

csharp
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

Controller web API thường nên kế thừa từ ControllerBase thay vì từ Controller. Controller kế thừa từ ControllerBase và thêm hỗ trợ cho view, vì vậy nó dành cho việc xử lý trang web, không phải yêu cầu web API. Nếu cùng một controller phải hỗ trợ cả view lẫn web API, hãy kế thừa từ Controller.

Lớp ControllerBase cung cấp nhiều thuộc tính và phương thức hữu ích để xử lý các yêu cầu HTTP. Ví dụ, CreatedAtAction trả về status code 201:

csharp
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
    pet.Id = _petsInMemoryStore.Any() ? 
             _petsInMemoryStore.Max(p => p.Id) + 1 : 1;
    _petsInMemoryStore.Add(pet);

    return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
}

Bảng sau chứa các ví dụ về phương thức trong ControllerBase:

Phương thứcGhi chú
BadRequestTrả về status code 400.
NotFoundTrả về status code 404.
PhysicalFileTrả về một file.
TryUpdateModelAsyncGọi model binding (ràng buộc model).
TryValidateModelGọi model validation (xác thực model).

Attribute

Namespace Microsoft.AspNetCore.Mvc cung cấp các attribute có thể được dùng để cấu hình hành vi của các controller web API và action method. Ví dụ sau sử dụng attribute để chỉ định HTTP action verb được hỗ trợ và các HTTP status code đã biết có thể được trả về:

csharp
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
    pet.Id = _petsInMemoryStore.Any() ? 
             _petsInMemoryStore.Max(p => p.Id) + 1 : 1;
    _petsInMemoryStore.Add(pet);

    return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
}

Dưới đây là một số ví dụ về các attribute có sẵn:

AttributeGhi chú
[Route]Chỉ định pattern URL cho controller hoặc action.
[Bind]Chỉ định prefix và thuộc tính để đưa vào model binding.
[HttpGet]Xác định một action hỗ trợ HTTP GET action verb.
[Consumes]Chỉ định các kiểu dữ liệu mà một action chấp nhận.
[Produces]Chỉ định các kiểu dữ liệu mà một action trả về.

Attribute ApiController

Attribute [ApiController] có thể được áp dụng cho lớp controller để kích hoạt các hành vi opinionated (có chủ kiến), đặc thù cho API sau:

Attribute trên các controller cụ thể

Attribute [ApiController] có thể được áp dụng cho các controller cụ thể, như trong ví dụ sau từ template dự án:

csharp
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

Attribute trên nhiều controller

Một cách để sử dụng attribute trên nhiều controller là tạo lớp controller cơ sở tùy chỉnh được annotate với attribute [ApiController]:

csharp
[ApiController]
public class MyControllerBase : ControllerBase
{
}
csharp
[Produces(MediaTypeNames.Application.Json)]
[Route("[controller]")]
public class PetsController : MyControllerBase

Attribute trên assembly

Attribute [ApiController] có thể được áp dụng cho assembly. Khi được áp dụng cho assembly, tất cả controller trong assembly đều có attribute [ApiController]. Áp dụng attribute ở cấp assembly cho file Program.cs:

csharp
using Microsoft.AspNetCore.Mvc;
[assembly: ApiController]

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

Yêu cầu attribute routing

Attribute [ApiController] làm cho attribute routing (định tuyến bằng attribute) trở thành yêu cầu bắt buộc. Ví dụ:

csharp
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase

Các action không thể truy cập thông qua conventional routes (định tuyến thông thường) được định nghĩa bởi UseEndpoints, UseMvc, hoặc UseMvcWithDefaultRoute.

Phản hồi HTTP 400 tự động

Attribute [ApiController] làm cho các lỗi xác thực model (model validation errors) tự động kích hoạt phản hồi HTTP 400. Do đó, đoạn code sau không cần thiết trong action method:

csharp
if (!ModelState.IsValid)
{
    return BadRequest(ModelState);
}

ASP.NET Core MVC sử dụng action filter ModelStateInvalidFilter để thực hiện kiểm tra trên.

Phản hồi BadRequest mặc định

Kiểu phản hồi mặc định cho phản hồi HTTP 400 là ValidationProblemDetails. Ví dụ về body phản hồi được serialize:

json
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "|7fb5e16a-4c8f23bbfc974667.",
  "errors": {
    "": [
      "A non-empty request body is required."
    ]
  }
}

Loại ValidationProblemDetails:

Ghi log phản hồi 400 tự động

Để ghi log các phản hồi 400 tự động, đặt thuộc tính delegate InvalidModelStateResponseFactory để thực hiện xử lý tùy chỉnh:

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        var builtInFactory = options.InvalidModelStateResponseFactory;

        options.InvalidModelStateResponseFactory = context =>
        {
            var logger = context.HttpContext.RequestServices
                                .GetRequiredService<ILogger<Program>>();

            // Thực hiện ghi log ở đây.
            // ...

            // Gọi hành vi mặc định.
            return builtInFactory(context);
        };
    });

Tắt phản hồi 400 tự động

Để tắt hành vi 400 tự động, đặt thuộc tính SuppressModelStateInvalidFilter thành true:

csharp
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
    });

Suy luận tham số nguồn ràng buộc

Attribute nguồn ràng buộc (binding source attribute) định nghĩa vị trí tìm giá trị của tham số action. Các attribute nguồn ràng buộc sau tồn tại:

AttributeNguồn ràng buộc
[FromBody]Request body
[FromForm]Form data trong request body
[FromHeader]Request header
[FromQuery]Tham số query string của request
[FromRoute]Route data từ request hiện tại
[FromServices]Request service được inject như tham số action
[AsParameters]Tham số phương thức

Cảnh báo: Không sử dụng [FromRoute] khi các giá trị có thể chứa %2f (tức là /). %2f sẽ không được unescape thành /. Sử dụng [FromQuery] nếu giá trị có thể chứa %2f.

Attribute [ApiController] áp dụng các quy tắc suy luận cho nguồn dữ liệu mặc định của tham số action. Các quy tắc suy luận nguồn ràng buộc hoạt động như sau:

Tắt quy tắc suy luận

Để tắt suy luận nguồn ràng buộc, đặt SuppressInferBindingSourcesForParameters thành true:

csharp
using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .ConfigureApiBehaviorOptions(options =>
    {
        options.SuppressConsumesConstraintForFormFileParameters = true;
        options.SuppressInferBindingSourcesForParameters = true;
        options.SuppressModelStateInvalidFilter = true;
        options.SuppressMapClientErrors = true;
        options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
            "https://httpstatuses.com/404";
        options.DisableImplicitFromServicesParameters = true;
    });

Suy luận yêu cầu Multipart/form-data

Attribute [ApiController] áp dụng quy tắc suy luận cho các tham số action kiểu IFormFileIFormFileCollection. Kiểu nội dung request multipart/form-data được suy luận cho các kiểu này.

Để tắt hành vi mặc định, đặt thuộc tính SuppressConsumesConstraintForFormFileParameters thành true.

Chi tiết vấn đề cho các status code lỗi

MVC biến đổi kết quả lỗi (kết quả có status code 400 hoặc cao hơn) thành kết quả với ProblemDetails. Kiểu ProblemDetails dựa trên đặc tả RFC 7807 để cung cấp chi tiết lỗi máy-đọc được trong phản hồi HTTP.

Xem đoạn code sau trong controller action:

csharp
if (pet == null)
{
    return NotFound();
}

Phương thức NotFound tạo ra HTTP status code 404 với body ProblemDetails. Ví dụ:

json
{
  type: "https://tools.ietf.org/html/rfc7231#section-6.5.4",
  title: "Not Found",
  status: 404,
  traceId: "0HLHLV31KRN83:00000001"
}

Tắt phản hồi ProblemDetails

Việc tạo tự động ProblemDetails cho các status code lỗi bị tắt khi thuộc tính SuppressMapClientErrors được đặt thành true.

Định nghĩa kiểu nội dung request được hỗ trợ với attribute [Consumes]

Theo mặc định, một action hỗ trợ tất cả các kiểu nội dung request có sẵn. Ví dụ, nếu ứng dụng được cấu hình để hỗ trợ cả JSON và XML input formatters, một action hỗ trợ nhiều kiểu nội dung, bao gồm application/jsonapplication/xml.

Attribute [Consumes] cho phép một action giới hạn các kiểu nội dung request được hỗ trợ. Áp dụng attribute [Consumes] cho một action hoặc controller, chỉ định một hoặc nhiều kiểu nội dung:

csharp
[HttpPost]
[Consumes("application/xml")]
public IActionResult CreateProduct(Product product)

Attribute [Consumes] cũng cho phép action ảnh hưởng đến việc lựa chọn của nó dựa trên kiểu nội dung của request đến bằng cách áp dụng ràng buộc kiểu. Xem ví dụ sau:

csharp
[ApiController]
[Route("api/[controller]")]
public class ConsumesController : ControllerBase
{
    [HttpPost]
    [Consumes("application/json")]
    public IActionResult PostJson(IEnumerable<int> values) =>
        Ok(new { Consumes = "application/json", Values = values });

    [HttpPost]
    [Consumes("application/x-www-form-urlencoded")]
    public IActionResult PostForm([FromForm] IEnumerable<int> values) =>
        Ok(new { Consumes = "application/x-www-form-urlencoded", Values = values });
}

Trong đoạn code trên, ConsumesController được cấu hình để xử lý các request gửi đến URL https://localhost:5001/api/Consumes. Cả hai action PostJsonPostForm xử lý POST request với cùng URL. Nếu không có attribute [Consumes] áp dụng ràng buộc kiểu, sẽ xảy ra ngoại lệ khớp mơ hồ (ambiguous match exception).

Tài nguyên bổ sung