.NET 6當中的Web API版本控制
大家好,我是張飛洪,感謝您的閱讀,我會不定期和你分享學習心得,希望我的文章能成為你成長路上的墊腳石,讓我們一起精進。
為了了解ASP.NET Core Web API的版本控制,我們必須了解API中的一些版本控制策略,然後將API版本控制與OpenAPI集成,以便我們可以在Swagger UI中看到版本化的API。
1 版本控制及策略
1.1 什麼是API版本控制?
API版本控制的目的是為了解決介面運維的問題。隨著時間推移,我們希望對那些調用API的前端人員,都有一個固定不變的API調用規則和策略。因為需求會變化,業務會增長,如果我們對API的設計沒有進行版本控制,那麼依賴API的用戶將變得無所適從,加上團隊人員的變遷,這會大大降低我們的聯調效率。
這就是我們為什麼要進行API版本控制的目的所在。那麼,我們如何對API進行版本化呢?
1.2 API版本控制策略
我們這裡討論三種最常用的API版本控制策略。
1)URI路徑版本控制
URI路徑策略很受歡迎,因為它更易於實現。一般我們會在URI路徑的某個地方插入一個版本指示符,如v1或v2,如下所示:
//iot.com/api/v1/products
以上是版本1,如果要升級為版本2,我們直接將v1改成v2即可:
//iot.com/api/v2/products
注意在切換API版本時,為了獲得正確的API返回的內容,原來的URI作為快取鍵可能會失效。基於路徑的版本控制很通用,幾乎大部分的平台或者語言都支援這種方法,幾乎成為了一種默認的標準,我們的案例程式碼默認也是採用這種策略。
2)Header版本控制
使用Header(頭部)進行版本控制,頭部一個謂詞,並且有一個頭部值,該值就是調用者需要分辨的版本號,如以下示例內容:
GET /api/products HTTP/1.1 Host: localhost:5001 Content-Type: application/json x-api-version: 2
此策略有個好處是它不會損壞URI。但是,在客戶端使用這些類型的API會比較麻煩一些。
3)查詢字元串版本控制
查詢字元串(Query string)根據API的使用者的需要,使用查詢字元串指定API的版本。,如果請求中沒有查詢字元串,則應該具有API的隱式默認版本。我們看一個示例:
//iot.com/api/products?api-version=2
以上三種策略都有各自的使用場景,具體應該選擇哪一個,取決於消費方法以及未來的規劃。
1.3 廢棄的API
我們可能會碰到一種需求,就是希望告知API調用方,哪些API不再推薦使用。比如一旦某個API版本在未來幾個月沒有人使用,我們希望刪除該API:
[ApiVersion("1.0", Deprecated = true)]
具體使用很簡單,這是Microsoft.AspNetCore.Mvc名稱空間下的使用方式,凡是加上這種特性的API都會別廢棄使用。
以上我們了解API版本控制的一些理論介紹,接下來我們通過程式碼來實現版本控制,以及如何將它們與OpenAPI集成方便在Swagger UI中查看。
2 API版本控制與OpenAPI的集成
2.1 API版本控制
本文是基於我影片的項目程式碼,所以在下面的程式碼連貫性上可能對您會有影響,但是整體上不影響您的理解。
如果您想查看完整的程式碼,可以訂閱我的影片,不勝感覺。
為了通過程式碼實現版本控制,我們需要切換到Iot.WebApi項目下進行,我們先在該項目下安裝兩個NuGet包:
dotnet add package Microsoft.AspNetCore.Mvc.Versioning
dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
第一個包是基於ASP.NET Core Mvc的版本服務,第二個包用於查找URL和HTTP方法、查找Controller(控制器)和Action元數據的一些功能。
接著,我們在Controller目錄下創建兩個文件夾,v1和v2,我們原先創建的控制器全部默認遷移到v1下,並修改一下相關的名稱空間,原來是:
Iot.WebApi.Controllers
現在改成:
Iot.WebApi.Controllers.v1
然後,我們修改抽象基類ApiContoller頭部的特性:
[ApiVersion("1.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public abstract class ApiController : ControllerBase
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ??= HttpContext.RequestServices.GetService<IMediator>();
}
我們添加了一個ApiVersion特性,並指定版本號,更新Route為動態API版本,所有繼承該基類的控制器都會標記上版本號。
我們還可以通過Deprecated來棄用WeatherForecast介面:
namespace Travel.WebApi.Controllers.v1 {
[ApiVersion("1.0", Deprecated = true)]
public class WeatherForecastController : ApiController { …} }
廢棄了一個介面,我們一般會創建一個新的介面版本,我們在v2文件夾下創建一個新的WeatherForecast.cs文件,程式碼如下所示:
namespace Travel.WebApi.Controllers.v2 {
[ApiVersion("2.0")]
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
public class WeatherForecastController : ControllerBase
{
…
[HttpPost]
public IEnumerable<WeatherForecast> Post(string city) {
var rng = new Random();
return Enumerable.Range(1,5).Select(index => new WeatherForecast
{
…
City = city}).ToArray();
}
}
}
新舊介面的主要區別是HTTP方法,在版本1中,必須發送一個GET請求以獲取日期和溫度數據,而在版本2中,必須使用查詢參數city發送一個POST請求。
因此,API必須具有版本控制,以避免中斷第一個版本的API導致的問題。
帶有查詢的POST請求不是好的做法,因為它是非冪等的,而GET、PUT和DELETE用於冪等請求。這裡先將就用著。
2.2 OpenAPI
我們先在Iot.WebApi的根目錄中創建一個新文件夾並命名為Helpers。然後創建兩個C#文件,SwagerOptions.cs和SwaggerDefaultValue.cs,ConfigureSwaggerOptions.cs如下所示:
using System;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace IoT.WebApi.OpenApi {
public class ConfigureSwaggerOptions : IConfigureOptions<SwaggerGenOptions>
{
…
public void Configure(SwaggerGenOptions options) {…}
private static OpenApiInfo CreateInfoForApiVersion (ApiVersionDescription description) {…}
}
}
這裡有兩個方法:Configure和OpenApiInfo,下面是Configure方法的程式碼塊:
public void Configure(SwaggerGenOptions options)
{
foreach (var description in _provider.ApiVersionDescriptions)
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
Configure方法的作用是為每個新發現的API版本添加一個Swagger文檔。下面是OpenApiInfo方法的程式碼塊:
private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info = new OpenApiInfo
{
Title = "Travel Tour",
Version = description.ApiVersion.ToString(),
Description = "Web Service for Travel Tour.",
Contact = new OpenApiContact
{
Name = "IT Department",
Email = "[email protected]",
Url = new Uri("//traveltour.xyz/support")
}
};
if (description.IsDeprecated)
info.Description += " <strong>This API version of Travel Tour has been deprecated.</strong>";
return info;
}
此程式碼用於Swagger相關資訊設置,如應用程式的標題、版本、描述、聯繫人姓名、聯繫人電子郵件和URL。
我們再看下SwaggerDefaultValues.cs:
public class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
//
}
}
SwaggerDefaultValues 會重寫並替換Startup.cs中的services.AddSwaggerGen()。下面是Apply方法的程式碼:
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
if (operation.Parameters == null)
return;
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(
pd => pd.Name == parameter.Name);
parameter.Description ??= description.ModelMetadata.Description;
if (parameter.Schema.Default == null && description.DefaultValue != null)
parameter.Schema.Default = new OpenApiString(description.DefaultValue.ToString());
parameter.Required |= description.IsRequired;
}
Apply方法允許Swagger生成器添加API資源管理器的所有相關元數據。
接下來我們更新一下Startup.cs文件,在ConfigureServices中找到AddSwageGen方法,然後使用下面程式碼進行替換:
services.AddSwaggerGen(c =>
{
c.OperationFilter<SwaggerDefaultValues>();
});
這裡使用過濾器配置我們之前創建的SwaggerDefaultValue。接下來在AddSwaggerGen方法後面給ConfigureSwaggerOptions設置服務生命周期:
services.AddTransient<IConfigureOptions
我們還要添加對ApiVersioning的註冊(Microsoft.AspNetCore.Mvc.Versioning):
services.AddApiVersioning(config =>
{
config.DefaultApiVersion = new ApiVersion(1, 0);
config.AssumeDefaultVersionWhenUnspecified = true;
config.ReportApiVersions = true;
});
上面的程式碼在服務集合中添加了版本控制,包括定義默認API版本和API支援的版本。
我們繼續在AddApiVersioning下面添加API Explorer(Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer):
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
});
該程式碼添加了一個API資源管理器,它的格式:’v’major[.minor][status] 。
現在在Configure方法中添加一個參數。將其命名為provider,類型為IApiVersionDescriptionProvider,如下所示:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IApiVersionDescriptionProvider provider)
這裡涉及到的是有關API版本的資訊,我們看下Configure中的UseSwaggerUI方法:
app.UseSwaggerUI(c =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint(
$"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
以上通過循環為每個發現的API版本構建一個Swagger訪問地址。
現在,讓我們運行程式並查看程式碼的結果。讓我們看看Swagger UI,WeatherForecast的測試版本1 API和版本2 的API,看看如果我們發送請求,它們是否正常工作。您可以在下面的截圖中看到效果,我們可以選擇要檢查的API版本:
我們可以看到v1和v2的WeatherForecast介面是不一樣的,v1的版本被拋棄了,所以顯示成灰色的。
而v2版本是正常的:
我們可以隨便傳入一個City參數,然後就可以看到返回記錄了: