Nguon: Microsoft Learn · .NET 8.0

Tạo tài liệu OpenAPI

Nguồn: Generate OpenAPI documents

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 OpenAPI model đượ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 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 (ID của thao tác) đượ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 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. Response mặc định này được điền dưới status code 200 trong định nghĩa OpenAPI.

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(); }); ``

Các union type Results<TResult1,TResult2,TResultN> khai báo rằng route handler trả về nhiều loại IResult concrete, và bất kỳ loại nào trong số đó triển khai IEndpointMetadataProvider sẽ đóng góp vào metadata của endpoint.

Các union type triển khai implicit cast operator (toán tử ép kiểu ngầm định). Các operator này cho phép compiler tự động chuyển đổi các kiểu được chỉ định trong các tham số generic thành instance của union type. Khả năng này có thêm lợi ích là cung cấp kiểm tra compile-time rằng route handler chỉ trả về các kết quả mà nó khai báo. Việc cố gắng trả về kiểu không được khai báo là một trong các tham số generic của Results<TResult1,TResult2,TResultN> dẫn đến lỗi compilation.

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");

Ngoài extension method Accepts, kiểu tham số có thể mô tả annotation của chính nó bằng cách triển khai interface IEndpointParameterMetadataProvider. Ví dụ, kiểu Todo sau thêm annotation yêu cầu request body với content-type application/xml.

csharp
public class Todo : IEndpointParameterMetadataProvider
{
    public static void PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder)
    {
        builder.Metadata.Add(new ConsumesAttribute(typeof(Todo), isOptional: false, "application/xml"));
    }
}

Khi không có annotation tường minh nào được cung cấp, framework cố gắng xác định kiểu request mặc định nếu có tham số request body trong endpoint handler. Việc suy luận sử dụng các heuristic (phỏng đoán) sau để tạo ra annotation:

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