Nguon: Microsoft Learn · .NET 8.0

Tổng quan về hỗ trợ OpenAPI trong ứng dụng ASP.NET Core API

Nguồn: Overview of OpenAPI support in ASP.NET Core API apps

Đặc tả OpenAPI là một tiêu chuẩn không phụ thuộc ngôn ngữ lập trình để tài liệu hóa HTTP APIs. Tiêu chuẩn này được hỗ trợ trong Minimal APIs thông qua kết hợp các API tích hợp sẵn và các thư viện mã nguồn mở. Có ba khía cạnh quan trọng của tích hợp OpenAPI trong ứng dụng:

Minimal APIs cung cấp hỗ trợ tích hợp để tạo thông tin về các endpoint trong ứng dụng qua gói Microsoft.AspNetCore.OpenApi. Việc phơi bày định nghĩa OpenAPI được tạo ra qua giao diện trực quan yêu cầu gói của bên thứ ba.

Để biết thông tin về hỗ trợ OpenAPI trong controller-based APIs, xem phiên bản .NET 9 của bài viết này.

Đoạn code sau được tạo bởi template ASP.NET Core minimal web API và sử dụng OpenAPI:

csharp
using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();

app.Run();

internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

Trong đoạn code được tô sáng trên:

Gói NuGet Microsoft.AspNetCore.OpenApi

ASP.NET Core cung cấp gói Microsoft.AspNetCore.OpenApi để tương tác với các đặc tả OpenAPI cho endpoint. Gói này đóng vai trò là liên kết giữa các model OpenAPI được định nghĩa trong gói Microsoft.AspNetCore.OpenApi và các endpoint được định nghĩa trong Minimal APIs. Gói cung cấp API để kiểm tra các tham số, phản hồi và metadata của endpoint để tạo ra loại annotation OpenAPI dùng để mô tả endpoint.

Microsoft.AspNetCore.OpenApi được thêm vào file project như PackageReference:

xml
<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>    
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.*-*" />
    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
  </ItemGroup>

</Project>

Khi sử dụng Swashbuckle.AspNetCore với Microsoft.AspNetCore.OpenApi, phải dùng Swashbuckle.AspNetCore 6.4.0 trở lên. Microsoft.OpenApi 1.4.3 trở lên phải được sử dụng để tận dụng copy constructor trong các lần gọi WithOpenApi.

Thêm annotation OpenAPI vào endpoint qua WithOpenApi

Gọi WithOpenApi trên endpoint sẽ thêm vào metadata của endpoint. Metadata này có thể được:

csharp
app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>
{
    todo.Id = id;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi();

Sửa đổi annotation OpenAPI trong WithOpenApi

Phương thức WithOpenApi nhận một hàm có thể được dùng để sửa đổi annotation OpenAPI. Ví dụ, trong đoạn code sau, một mô tả được thêm vào tham số đầu tiên của endpoint:

csharp
app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>
{
    todo.Id = id;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi(generatedOperation =>
{
    var parameter = generatedOperation.Parameters[0];
    parameter.Description = "The ID associated with the created Todo";
    return generatedOperation;
});

Thêm operation ID vào OpenAPI

Operation ID được dùng để định danh duy nhất một endpoint nhất định trong OpenAPI. Extension method WithName có thể được dùng để đặt operation ID cho một phương thức.

csharp
app.MapGet("/todoitems2", async (TodoDb db) =>
    await db.Todos.ToListAsync())
    .WithName("GetToDoItems");

Ngoài ra, thuộc tính OperationId có thể được đặt trực tiếp trên annotation OpenAPI.

csharp
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        OperationId = "GetTodos"
    });

Thêm tag vào mô tả OpenAPI

OpenAPI hỗ trợ sử dụng tag object để phân loại các operation. Các tag này thường được dùng để nhóm các operation trong Swagger UI. Các tag này có thể được thêm vào một operation bằng cách gọi extension method WithTags trên endpoint với các tag mong muốn.

csharp
app.MapGet("/todoitems", async (TodoDb db) =>
    await db.Todos.ToListAsync())
    .WithTags("TodoGroup");

Ngoài ra, danh sách OpenApiTags có thể được đặt trên annotation OpenAPI qua extension method WithOpenApi.

csharp
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        Tags = new List<OpenApiTag> { new() { Name = "Todos" } }
    });

Thêm tóm tắt hoặc mô tả endpoint

Tóm tắt và mô tả endpoint có thể được thêm bằng cách gọi extension method WithOpenApi. Trong đoạn code sau, các tóm tắt được đặt trực tiếp trên annotation OpenAPI.

csharp
app.MapGet("/todoitems2", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        Summary = "This is a summary",
        Description = "This is a description"
    });

Loại trừ mô tả OpenAPI

Trong ví dụ sau, endpoint /skipme bị loại trừ khỏi việc tạo mô tả OpenAPI:

csharp
using Microsoft.AspNetCore.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/swag", () => "Hello Swagger!")
    .WithOpenApi();
app.MapGet("/skipme", () => "Skipping Swagger.")
                    .ExcludeFromDescription();

app.Run();

Đánh dấu API là lỗi thời (obsolete)

Để đánh dấu một endpoint là lỗi thời, đặt thuộc tính Deprecated trên annotation OpenAPI.

csharp
app.MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .WithOpenApi(operation => new(operation)
    {
        Deprecated = true
    });

Mô tả các kiểu response

OpenAPI hỗ trợ cung cấp mô tả về các response được trả về từ API. Minimal APIs hỗ trợ ba chiến lược để đặt kiểu response của endpoint:

Extension method Produces có thể được dùng để thêm metadata Produces vào endpoint. Khi không có tham số nào được cung cấp, extension method sẽ điền metadata cho kiểu được nhắm đến dưới status code 200 và content type application/json.

csharp
app
    .MapGet("/todos", async (TodoDb db) => await db.Todos.ToListAsync())
    .Produces<IList<Todo>>();

Sử dụng TypedResults trong triển khai route handler của endpoint sẽ tự động bao gồm metadata kiểu response cho endpoint. Ví dụ, đoạn code sau tự động annotate endpoint với response dưới status code 200 với content type application/json.

csharp
app.MapGet("/todos", async (TodoDb db) =>
{
    var todos = await db.Todos.ToListAsync());
    return TypedResults.Ok(todos);
});

Đặt response cho ProblemDetails

Khi đặt kiểu response cho các endpoint có thể trả về ProblemDetails response, extension method ProducesProblem, ProducesValidationProblem, hoặc TypedResults.Problem có thể được dùng để thêm annotation phù hợp vào metadata của endpoint.

Khi không có annotation tường minh nào được cung cấp bởi một trong các chiến lược trên, framework cố gắng xác định kiểu response mặc định bằng cách kiểm tra chữ ký của response.

Nhiều kiểu response

Nếu một endpoint có thể trả về các kiểu response khác nhau trong các tình huống khác nhau, bạn có thể cung cấp metadata theo các cách sau:

``csharp app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) => await db.Todos.FindAsync(id) is Todo todo ? Results.Ok(todo) : Results.NotFound()) .Produces<Todo>(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound); ``

``csharp app.MapGet("/book/{id}", Results<Ok<Book>, NotFound> (int id, List<Book> bookList) => { return bookList.FirstOrDefault((i) => i.Id == id) is Book book ? TypedResults.Ok(book) : TypedResults.NotFound(); }); ``

Mô tả request body và tham số

Ngoài việc mô tả các kiểu được trả về bởi endpoint, OpenAPI cũng hỗ trợ annotate các input được API sử dụng. Các input này thuộc hai danh mục:

Framework tự động suy luận các kiểu cho request parameter trong path, query, và header string dựa trên chữ ký của route handler.

Để định nghĩa kiểu input được truyền dưới dạng request body, cấu hình các thuộc tính bằng cách sử dụng extension method Accepts để định nghĩa kiểu object và content type mà request handler mong đợi. Trong ví dụ sau, endpoint nhận object Todo trong request body với content-type application/xml.

csharp
app.MapPost("/todos/{id}", (int id, Todo todo) => ...)
  .Accepts<Todo>("application/xml");

Hỗ trợ API versioning (phiên bản API)

Minimal APIs hỗ trợ API versioning qua gói Asp.Versioning.Http. Ví dụ về cấu hình versioning với Minimal APIs có thể tìm thấy trong repo API versioning.

Mã nguồn ASP.NET Core OpenAPI trên GitHub