Huong dan thuc te ve API Versioning trong ASP.NET Core

API versioning la cach quan ly cac thay doi cua API theo thoi gian bang cach gan nhan cho tung phien ban. Nho do, cac ung dung, script va dich vu dang su dung phien ban cu van co the hoat dong khi API duoc mo rong hoac thay doi.

Thay vi duy tri mot API duy nhat va lien tuc thay doi, ban co the duy tri nhieu phien ban rieng biet, chang han v1 va v2. Phien ban cu tiep tuc phuc vu cac client hien tai, trong khi client moi co the su dung tinh nang, truong du lieu hoac cach xu ly moi.

Vi du don gian

Gia su endpoint ban dau tra ve ten nguoi dung trong mot truong duy nhat:

GET /v1/users/123
{
  "name": "Jane Doe"
}

Sau do, ban muon tach ten thanh ten va ho:

GET /v2/users/123
{
  "firstName": "Jane",
  "lastName": "Doe"
}

Neu thay doi truc tiep endpoint cu, cac ung dung dang cho doi truong name co the bi loi. Thay vao do, ban tao /v2/users/123. Client cu tiep tuc goi v1 cho den khi san sang chuyen sang v2.

Ve ban chat, versioning la mot co che duy tri hop dong giua nha cung cap API va cac ben tieu thu. No cho phep API thuc hien ca nhung thay doi khong tuong thich ma khong bat buoc moi client phai cap nhat cung luc.

Vi sao API versioning quan trong?

  1. Thay doi khong tuong thich la dieu kho tranh khoi. Cau truc phan hoi, ten truong, kieu du lieu hoac quy tac xu ly co the thay doi.
  2. Ban khong kiem soat chu ky phat hanh cua client. Ung dung di dong, dich vu tich hop va he thong cua khach hang co the cap nhat vao cac thoi diem khac nhau.
  3. Tinh on dinh tao niem tin. Client co the tiep tuc su dung phien ban da duoc kiem thu.
  4. Ho tro phat trien tung buoc. Ban co the trien khai, kiem thu va di chuyen sang phien ban moi theo tung giai doan.
  5. Cho phep xay dung chinh sach ngung ho tro ro rang. Client co the nhan biet phien ban nao dang bi loai bo va co thoi gian chuyen doi.

Bon cach versioning API trong ASP.NET Core

Thu vien Asp.Versioning ho tro bon chien luoc pho bien. Ban cung co the ket hop nhieu cach doc phien ban trong giai doan chuyen doi.

1. Versioning bang URL segment

Phien ban duoc dat truc tiep trong duong dan:

GET /api/v1/users
GET /api/v2/users

Uu diem: de nhin, de tim thay, de kiem thu bang trinh duyet, tuong thich tot voi cache, CDN va log.

Han che: URL thay doi moi khi ban tao phien ban moi va cach nay it thuan tuy REST hon, vi URL ly tuong nen dai dien cho tai nguyen thay vi phien ban.

Day la lua chon pho bien nhat vi ro rang, don gian va de debug.

2. Versioning bang query string

GET /api/users?api-version=1.0
GET /api/users?api-version=2.0

Uu diem: de trien khai, khong can thay doi cau truc route va de kiem thu thu cong.

Han che: client co the quen them tham so, mot so lop cache co the loai bo query string, URL dai hon va phien ban it noi bat trong log.

3. Versioning bang HTTP header

GET /api/users
X-Api-Version: 2.0

Uu diem: giu URL sach va tai nguyen van co cung dinh danh theo quan diem REST.

Han che: kho nhan biet khi chi nhin URL, khong the kiem thu don gian bang trinh duyet va kho debug neu he thong khong ghi log header.

4. Versioning bang media type

GET /api/users
Accept: application/json;v=2.0

Ban cung co the dung media type rieng cho nha cung cap:

Accept: application/vnd.myapi.v2+json

Uu diem: phu hop nhat voi tu tuong REST va HATEOAS, trong do URL dai dien cho tai nguyen con header quyet dinh dang bieu dien.

Han che: phuc tap hon voi client, kho phat hien, khong tien de kiem thu va it duoc su dung ngoai cac he thong dat nang yeu cau REST.

Bang so sanh nhanh

  • URL segment: kha nang nhan biet cao, do thuan REST thap, de su dung cao, pho bien nhat.
  • Query string: kha nang nhan biet trung binh, de su dung cao, duoc su dung pho bien.
  • Header: kha nang nhan biet thap, do thuan REST cao, muc do su dung trung binh.
  • Media type: kha nang nhan biet rat thap, do thuan REST cao nhat, kho su dung va it pho bien.

Neu can mot lua chon thuc te, URL segment thuong la phuong an an toan nhat. Header hoac media type phu hop khi he thong co ly do kien truc manh de giu URL khong gan voi phien ban.

Thuc hien API versioning trong ASP.NET Core

ASP.NET Core su dung thu vien Asp.Versioning de quan ly phien ban API. Thu vien nay ho tro ca Minimal API va API dua tren controller.

Chon goi NuGet theo mo hinh ung dung

  • Asp.Versioning.Http: dung cho Minimal API.
  • Asp.Versioning.Mvc: dung cho API dua tren controller.
  • Asp.Versioning.Mvc.ApiExplorer: tao metadata cho cong cu nhu Swagger va OpenAPI.
  • Asp.Versioning.OpenApi: ket noi thu vien versioning voi bo tao OpenAPI tich hop cua Microsoft.

Voi Minimal API, cai dat:

dotnet add package Asp.Versioning.Http --version 10.2.3
dotnet add package Asp.Versioning.Mvc.ApiExplorer --version 10.2.1
dotnet add package Asp.Versioning.OpenApi --version 10.2.3

Voi API dua tren controller, cai dat:

dotnet add package Asp.Versioning.Mvc --version 10.2.1
dotnet add package Asp.Versioning.Mvc.ApiExplorer --version 10.2.1
dotnet add package Asp.Versioning.OpenApi --version 10.2.3

Cau hinh API versioning

Trong Program.cs, goi AddApiVersioning() de dang ky cac dich vu versioning vao bo quan ly phu thuoc cua ASP.NET Core:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new Asp.Versioning.ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = Asp.Versioning.ApiVersionReader.Combine(
        new Asp.Versioning.UrlSegmentApiVersionReader(),
        new Asp.Versioning.HeaderApiVersionReader("X-Api-Version"),
        new Asp.Versioning.QueryStringApiVersionReader("api-version")
    );
})
.AddApiExplorer(options =>
{
    options.GroupNameFormat = "'v'VVV";
    options.SubstituteApiVersionInUrl = true;
})
.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi().WithDocumentPerVersion();
}

app.Run();

Giai thich AddApiVersioning

  • DefaultApiVersion = new ApiVersion(1, 0): dat phien ban mac dinh la 1.0.
  • AssumeDefaultVersionWhenUnspecified = true: neu client khong gui phien ban, request se duoc xu ly theo phien ban mac dinh thay vi bi tu choi.
  • ReportApiVersions = true: them cac header api-supported-versions va, neu can, api-deprecated-versions vao response.
  • ApiVersionReader.Combine(...): cho phep doc phien ban tu nhieu vi tri. Trong vi du tren, thu vien doc URL segment, header X-Api-Version va query string api-version.

Cau hinh ket hop nhieu cach doc phien ban co the huu ich trong giai doan chuyen doi. Tuy nhien, khi API da on dinh, nen chon mot chien luoc duy nhat, cong bo ro rang va loai bo su mo ho khi xac dinh phien ban.

Vi du, neu chon URL segment:

options.ApiVersionReader = new UrlSegmentApiVersionReader();

Giai thich AddApiExplorer

  • GroupNameFormat = "'v'VVV": dinh dang ten nhom tai lieu. Phien ban 1.0 thanh v1, con 1.1 thanh v1.1.
  • SubstituteApiVersionInUrl = true: thay placeholder {version:apiVersion} bang phien ban thuc khi tao duong dan trong tai lieu OpenAPI.

AddOpenApi va WithDocumentPerVersion

AddOpenApi() dang ky dich vu tao tai lieu OpenAPI tich hop cua Microsoft. Khi ket hop voi API Explorer, he thong co the tao tai lieu rieng cho tung phien ban.

WithDocumentPerVersion() tu dong tao cac tai lieu nhu /openapi/v1.json va /openapi/v2.json. Moi tai lieu chi chua cac route thuoc phien ban tuong ung.

Versioning cho Minimal API

Gia su ung dung co mot route group chua endpoint POST:

var workGroup = app.MapGroup("/works");

workGroup.MapPost("/", async (Work work, WorkDb db) =>
{
    db.Works.Add(work);
    await db.SaveChangesAsync();

    return Results.Created($"/works/{work.Id}", work);
});

De them versioning, chuyen route group thanh versioned API:

var worksApi = app.NewVersionedApi("Works");

var workGroupV1 = worksApi.MapGroup("/api/v{version:apiVersion}/works")
    .HasApiVersion(1.0);

var workGroupV2 = worksApi.MapGroup("/api/v{version:apiVersion}/works")
    .HasApiVersion(2.0);

workGroupV1.MapPost("/", async (Work work, WorkDb db) =>
{
    db.Works.Add(work);
    await db.SaveChangesAsync();

    return Results.Created($"/api/v1/works/{work.Id}", work);
});

workGroupV2.MapPost("/", async (Work work, WorkDb db) =>
{
    db.Works.Add(work);
    await db.SaveChangesAsync();

    var response = new WorkV2Response(
        work.Id,
        work.Name,
        work.TimeStart,
        work.TimeEnd,
        work.IsComplete,
        work.IsComplete ? "Low" : "High"
    );

    return Results.Created($"/api/v2/works/{work.Id}", response);
});

Nhung diem thay doi quan trong:

  • {version:apiVersion} cho phep URL segment reader nhan biet phien ban tu URL nhu /api/v1/works.
  • HasApiVersion(1.0) va HasApiVersion(2.0) gan route group vao tung phien ban.
  • Header Location cua Results.Created duoc cap nhat de trung voi URL da version hoa.
  • Phien ban v2 co the tra ve mot kieu response khac ma khong anh huong client dang dung v1.

Co the goi hai phien ban tu file .http:

@WorkApi_HostAddress = https://localhost:7226/api/v1.0

POST {{WorkApi_HostAddress}}/works
Content-Type: application/json

{
  "name": "eat breakfast",
  "isComplete": true,
  "timeStart": "8:00:00",
  "timeEnd": "8:30:00"
}
@WorkApi_HostAddress = https://localhost:7226/api/v2.0

POST {{WorkApi_HostAddress}}/works
Content-Type: application/json

{
  "name": "eat breakfast",
  "isComplete": true,
  "timeStart": "8:00:00",
  "timeEnd": "8:30:00"
}

Phien ban 1 su dung /api/v1.0, con phien ban 2 su dung /api/v2.0.

Ban co the danh dau phien ban cu la deprecated ma khong xoa endpoint:

var workGroupV1 = worksApi.MapGroup("/api/v{version:apiVersion}/works")
    .HasApiVersion(1.0)
    .HasDeprecatedApiVersion(1.0);

Khi ReportApiVersions duoc bat, client se nhan thong tin thong qua cac header phien ban duoc ho tro va phien ban loi thoi.

Versioning cho Controller

Voi API dua tren controller, cach duoc khuyen nghi la tach controller theo tung phien ban. Cach nay dac biet huu ich khi cau truc response cua cac phien ban da khac nhau nhieu.

Controller cho phien ban 1

using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;

namespace YourApp.Controllers.V1;

[ApiController]
[ApiVersion(1.0)]
[Route("api/v{version:apiVersion}/works")]
public class WorksController : ControllerBase
{
    private readonly WorkDb _db;

    public WorksController(WorkDb db)
    {
        _db = db;
    }

    [HttpPost]
    public async Task<IActionResult> Create(Work work)
    {
        _db.Works.Add(work);
        await _db.SaveChangesAsync();

        return Created($"/api/v1/works/{work.Id}", work);
    }
}

Controller cho phien ban 2

using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;

namespace YourApp.Controllers.V2;

[ApiController]
[ApiVersion(2.0)]
[Route("api/v{version:apiVersion}/works")]
public class WorksController : ControllerBase
{
    private readonly WorkDb _db;

    public WorksController(WorkDb db)
    {
        _db = db;
    }

    [HttpPost]
    public async Task<IActionResult> Create(Work work)
    {
        _db.Works.Add(work);
        await _db.SaveChangesAsync();

        var response = new WorkV2Response(
            work.Id,
            work.Name,
            work.TimeStart,
            work.TimeEnd,
            work.IsComplete,
            work.IsComplete ? "Low" : "High"
        );

        return Created($"/api/v2/works/{work.Id}", response);
    }
}

Hai controller deu co ten WorksController, nhung nam trong hai namespace khac nhau la Controllers.V1 va Controllers.V2. Day la quy uoc pho bien cho controller da version hoa.

Danh dau controller la phien ban loi thoi

[ApiController]
[ApiVersion(1.0, Deprecated = true)]
[Route("api/v{version:apiVersion}/works")]
public class WorksController : ControllerBase
{
    private readonly WorkDb _db;

    public WorksController(WorkDb db)
    {
        _db = db;
    }

    [HttpPost]
    public async Task<IActionResult> Create(Work work)
    {
        _db.Works.Add(work);
        await _db.SaveChangesAsync();

        return Created($"/api/v1/works/{work.Id}", work);
    }
}

Thuoc tinh VisibleInApiVersion

[VisibleInApiVersion] cho phep kiem soat viec hien thi cac thanh vien cua DTO theo tung phien ban. Ban khong can tao cac lop DTO rieng biet chi vi them hoac xoa mot thuoc tinh.

public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }

    [VisibleInApiVersion("2.0")]
    public string InternalSku { get; set; }
}

Trong vi du tren, Id va Name xuat hien o moi phien ban. InternalSku chi xuat hien tu phien ban 2.0.

Voi request den v1:

{
  "id": 1,
  "name": "Wireless Mouse"
}

Voi request den v2:

{
  "id": 1,
  "name": "Wireless Mouse",
  "internalSku": "WM-2024-XYZ"
}

Thuoc tinh khong chi bi bo khoi response ma con duoc loai khoi schema OpenAPI cua phien ban cu. Nhờ do, tai lieu cua tung phien ban phan anh dung hop dong ma client se nhan.

Gioi han pham vi phien ban

Ban co the dung bieu thuc pham vi:

[VisibleInApiVersion("[2.0,3.0)")]
  • [ va ]: dau moc bao gom.
  • ( va ): dau moc khong bao gom.
  • Bo trong mot dau moc de tao pham vi khong gioi han.
public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }

    [VisibleInApiVersion("[2.0,3.0)")]
    public string InternalSku { get; set; }

    [VisibleInApiVersion("[1.0,2.0]")]
    public string LegacyCategory { get; set; }

    [VisibleInApiVersion("[2.0,)")]
    public string NewField { get; set; }

    [VisibleInApiVersion("(,2.0)")]
    public string DeprecatedField { get; set; }
}

Neu muon truong xuat hien tu 2.0 va bi loai bo khi 4.0 duoc phat hanh:

[VisibleInApiVersion("[2.0,4.0)")]
public string InternalSku { get; set; }

Sunset header: thong bao thoi diem ngung ho tro API

Sunset la HTTP response header duoc dinh nghia trong RFC 8594. Header nay cho biet mot tai nguyen, chang han endpoint API, se ngung kha dung sau mot thoi diem cu the.

Vi du sau thong bao phien ban 1.0 se bi ngung vao ngay 31 thang 12 nam 2027, dong thoi tro nguoi dung den tai lieu chuyen doi:

builder.Services.AddApiVersioning(options =>
{
    options.ReportApiVersions = true;

    options.Policies.Sunset(1.0)
        .Effective(new DateTimeOffset(2027, 12, 31, 0, 0, 0, TimeSpan.Zero))
        .Link("https://yogihosting.com/docs/migrating-to-v2")
            .Title("Migration Guide")
            .Type("text/html");
});

Response den endpoint v1.0 co the tu dong chua:

Sunset: Thu, 31 Dec 2027 00:00:00 GMT
Link: <https://yogihosting.com/docs/migrating-to-v2>; rel="sunset"; title="Migration Guide"; type="text/html"

Nho thong tin nay, client va he thong giam sat co the chuan bi chuyen doi truoc han thay vi bat ngo nhan loi 404. Nen dung DateTimeOffset voi mui gio ro rang, khong dung DateTime khong co offset, de tranh sai lech ngay khi trien khai tren cac mui gio khac nhau.

Tich hop Swagger UI voi API versioning

Cai goi Swashbuckle.AspNetCore.SwaggerUI de phuc vu giao dien Swagger UI. Cach cau hinh giong nhau voi controller va Minimal API:

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi().WithDocumentPerVersion();

    app.UseSwaggerUI(options =>
    {
        foreach (var description in app.DescribeApiVersions().Reverse())
        {
            options.SwaggerEndpoint(
                $"/openapi/{description.GroupName}.json",
                description.GroupName.ToUpperInvariant());
        }
    });
}

app.DescribeApiVersions() lay danh sach cac phien ban API. Moi phien ban duoc gan voi mot tai lieu OpenAPI rieng. Vong lap dao nguoc danh sach de phien ban moi nhat duoc hien thi truoc.

Sau khi chay ung dung, mo duong dan /swagger de xem cac phien ban. Ban co the chon tai lieu tu danh sach phien ban va thu nghiem endpoint ngay trong giao dien.

Tich hop Scalar voi API versioning

Scalar la mot lua chon khac de hien thi tai lieu OpenAPI. Cai goi can dung la Scalar.AspNetCore:

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi().WithDocumentPerVersion();

    app.MapScalarApiReference(options =>
    {
        var descriptions = app.DescribeApiVersions();

        for (var i = 0; i < descriptions.Count; i++)
        {
            var description = descriptions[i];
            var isDefault = i == descriptions.Count - 1;

            options.AddDocument(
                description.GroupName,
                description.GroupName,
                isDefault: isDefault);
        }
    });
}

Mo duong dan /scalar de xem toan bo tai lieu API. Danh sach phien ban duoc hien thi trong giao dien, va ban co the chon phien ban muon xem.

Quy trinh versioning API nen ap dung

  • Chon mot chien luoc versioning phu hop va su dung nhat quan.
  • Cong bo phien ban trong tai lieu API, vi du OpenAPI.
  • Khong tao breaking change trong phien ban dang phuc vu client neu khong can thiet.
  • Danh dau phien ban cu la deprecated truoc khi xoa bo.
  • Cung cap header api-supported-versions va api-deprecated-versions.
  • Dung Sunset va Link de cong bo han ngung ho tro va tai lieu chuyen doi.
  • Tao tai lieu OpenAPI rieng cho tung phien ban.
  • Kiem tra breaking change tu dong trong quy trinh CI/CD.
  • Chi dung ApiVersionReader.Combine trong giai doan chuyen tiep, sau do nen chuyen ve mot cach doc phien ban duy nhat.

Ket luan

API versioning la thanh phan quan trong khi xay dung API co vong doi dai. Trong ASP.NET Core, thu vien Asp.Versioning cung cap giai phap day du cho ca Minimal API va controller.

Qua bai viet nay, ban da biet cach cau hinh phien ban, su dung URL segment, query string va header, tao route rieng cho tung phien ban, tach controller, danh dau API loi thoi va tao tai lieu OpenAPI rieng. Thuoc tinh [VisibleInApiVersion] con cho phep kiem soat tung truong DTO theo pham vi phien ban ma khong can duy tri nhieu lop DTO trung lap.

Ket hop versioning voi Swagger UI, Scalar, Sunset header va kiem tra tu dong trong CI/CD, ban co the phat trien API theo tung buoc ma van bao ve duoc cac client dang ton tai.

Chuc ban lap trinh .NET thanh cong!

Ma nguon tham khao: Tai ma nguon API Versioning

Bai tiep theo: Ho tro OpenAPI trong ung dung ASP.NET Core Web API