一旦 Web API 部署並開始使用後,它應該是可靠的,並且不應該因任何原因而中斷。另一方面,隨著需求的變化,我們需要更新 Web API 代碼,但這應該不破壞目前 API 的情況下完成,因此新舊版本的 Web API 都將處於活動狀態,功能也要正常。這時候就要靠 Web API 版本控制,我們靠它用於處理不同版本的Web API。微軟的 Microsoft.AspNetCore.Mvc.Versioning 可以讓我們輕易的完成此項目,但我在整合到 Swagger UI / Swashbuckle.AspNetCore 的時候碰到了一些關卡,所幸順利的解決了,以下是我的實作筆記。
開發環境
- Windows 11
- .NET 6
- Rider 2021.3.3
- Swashbuckle.AspNetCore 6.2.3
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer 5.0.0
安裝及設定
安裝套件
dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer --version 5.0.0
這套件依賴 Microsoft.AspNetCore.Mvc.Versioning
Step1. 在 DI Container 註冊 Api Version相關資訊
- 返回響應標頭中支援的版本資訊:option.ReportApiVersions = true
- 未提供版本請請時,使用預設版號:option.AssumeDefaultVersionWhenUnspecified = true
- 預設api版本號,支援時間或數字版本號:option.DefaultApiVersion = new ApiVersion(1, 0)
- 設定版本號方式,使用 ApiVersionReader.Combine 合併多種方式,目前支援以下幾種方式選擇 API 版本
MediaTypeApiVersionReader("api-version")
HeaderApiVersionReader("api-version")
QueryStringApiVersionReader("api-version")
UrlSegmentApiVersionReader()
代碼如下:
builder.Services.AddApiVersioning(option =>
{
//返回響應標頭中支援的版本資訊
option.ReportApiVersions = true;
//未提供版本請請時,使用預設版號
option.AssumeDefaultVersionWhenUnspecified = true;
//預設api版本號,支援時間或數字版本號
option.DefaultApiVersion = new ApiVersion(1, 0);
//支援MediaType、Header、QueryString 設定版本號;預設為 QueryString、UrlSegment
option.ApiVersionReader = ApiVersionReader.Combine(
new MediaTypeApiVersionReader("api-version"),
new HeaderApiVersionReader("api-version"),
new QueryStringApiVersionReader("api-version"),
new UrlSegmentApiVersionReader());
});
Step2. 使用 Api Version Middleware
app.UseApiVersioning();
Step3. 設定 Controller
我需要兩個版本,分別是 1.0、1.1,不同 namespace,相同的 class name,代碼如下
namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_0;
[ApiVersion("1.0", Deprecated = true)]
[ApiController]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return this.Ok(new
{
Version = 1.0,
Name = "1.0"
});
}
}
namespace Lab.Swashbuckle.AspNetCore6.Controllers.Employee.v1_1;
[ApiVersion("1.1")]
[ApiController]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return this.Ok(new
{
Version = 1.1,
Name = "1.1"
});
}
}
目錄結構如下:
選擇版本
QueryStringApiVersionReader
builder.Services.AddApiVersioning(option =>
{
option.ApiVersionReader = new QueryStringApiVersionReader("api-version");
});
不帶版本的請求,回傳了預設版本,這是我在 DI Container 的設定 option.AssumeDefaultVersionWhenUnspecified = true;
假使設定option.AssumeDefaultVersionWhenUnspecified = false則必須要帶入版號,否則將不允許使用
增加版本的 query string,?api-version=1.1 就可以訪問了
https://localhost:7236/api/Demo
可以看得出來版本的資訊
HeaderApiVersionReader
續上一個 Controller 代碼,這次在 DI 設定 Header 增加 api-version
builder.Services.AddApiVersioning(option =>
{
option.ApiVersionReader = new HeaderApiVersionReader("api-version");
});
執行結果如下:
MediaTypeApiVersionReader
續上一個 Controller 代碼,這次在 DI 設定 MediaType 增加 api-version
builder.Services.AddApiVersioning(option =>
{
option.ApiVersionReader = new MediaTypeApiVersionReader("api-version");
});
UrlSegmentApiVersionReader
DI Container 設定如下:
builder.Services.AddApiVersioning(option =>
{
option.ApiVersionReader = new MediaTypeApiVersionReader("api-version");
})
Controller 改一下,讓版號成為 URL [Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0", Deprecated = true)]
[ApiController]
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
public class DemoController : ControllerBase
{
...
}
[ApiVersion("1.1")]
[ApiController]
[Route("api/[controller]")]
[Route("api/v{version:apiVersion}/[controller]")]
public class DemoController : ControllerBase
{
...
}
執行結果如下
MapToApiVersion
允許將單個 API 操作對映到任何版本,下面的例子是在 1.1 增加一個 2.0
[ApiVersion("1.1")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class DemoController : ControllerBase
{
public IActionResult Get()
{
return this.Ok(new
{
Version = 1.1,
Name = "1.1"
});
}
[HttpGet, MapToApiVersion("2.0")]
public IActionResult GetV2()
{
return this.Ok(new
{
Version = 2.0,
Name = "2.0"
});
}
}
執行效果如下:
這樣設定我還不知道要怎麼在 Swagger UI 呈現,不過端點還是可以運作的
整合 Swagger UI for Swashbuckle.AspNetCore
到目前為止 Swagger 應該還跑不起來,DI Container 還要增加AddVersionedApiExplorer的設定
Step1. 設定 AddVersionedApiExplorer
builder.Services.AddVersionedApiExplorer(options =>
{
// add the versioned api explorer, which also adds IApiVersionDescriptionProvider service
// note: the specified format code will format the version as "'v'major[.minor][-status]"
options.GroupNameFormat = "'v'VVV";
// note: this option is only necessary when versioning by url segment. the SubstitutionFormat
// can also be used to control the format of the API version in route templates
options.SubstituteApiVersionInUrl = true;
});
Step2. 設定 Swagger Doc 文件資訊
builder.Services.AddSingleton<IConfigureOptions<SwaggerGenOptions>, ConfigureApiVersionSwaggerGenOptions>();
由於 Route 已經帶有版號,我們得讓 SwaggerDoc 的 Name 跟 SwaggerEndpoint 的 Name 對應起來
- IApiVersionDescriptionProvider.ApiVersionDescriptions:端點的版本號
- options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description)):API 文件資訊
public class ConfigureApiVersionSwaggerGenOptions : IConfigureOptions<SwaggerGenOptions>
{
private readonly IApiVersionDescriptionProvider _provider;
public ConfigureApiVersionSwaggerGenOptions(IApiVersionDescriptionProvider provider)
{
_provider = provider;
}
public void Configure(SwaggerGenOptions options)
{
foreach (var description in _provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}
private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
//產生 API 資訊
var info = new OpenApiInfo
{
Version = description.ApiVersion.ToString(),
Title = "Employee API",
Description =
@"<p>Sample API with versioning including Swagger.</p><p>Partly taken from <a href=""https://github.com/microsoft/aspnet-api-versioning"">this repository</a>.</p>",
TermsOfService = new Uri("https://example.com/terms"),
Contact = new OpenApiContact
{
Name = "Example Contact",
Url = new Uri("https://example.com/contact")
},
License = new OpenApiLicense
{
Name = "Example License",
Url = new Uri("https://example.com/license")
}
};
if (description.IsDeprecated)
{
info.Description +=
@"<p><strong><span style=""color:white;background-color:red"">VERSION IS DEPRECATED</span></strong></p>";
}
return info;
}
}
Step3. 設定 Swagger Enpoint,從 DI Container 取出 IApiVersionDescriptionProvider,組合出 Swagger Json 的 URL
app.UseSwaggerUI(
options =>
{
var provider = app.Services.GetService<IApiVersionDescriptionProvider>();
// build a swagger endpoint for each discovered API version
foreach (var description in provider.ApiVersionDescriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
options.SwaggerEndpoint(url,
description.GroupName.ToUpperInvariant());
}
});
參考資料
api-guidelines/Guidelines.md at master · microsoft/api-guidelines (github.com)
API versioning with ASP.NET Core and Swashbuckle | codingfreaks
Creating .NET Core API with versioning - PureSourceCode
Set up Swagger and API versioning in .NET 5 web API (nwb.one)
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET