大家好,我是張飛洪,感謝您的閱讀,我會不定期和你分享學習心得,希望我的文章能成為你成長路上的墊腳石,讓我們一起精進。
為了了解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,如下所示:
https://iot.com/api/v1/products
以上是版本1,如果要更新為版本2,我們直接将v1改成v2即可:
https://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的隐式預設版本。我們看一個示例:
https://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("https://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, ConfigureSwaggerOptions>();
我們還要添加對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(
#34;/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
以上通過循環為每個發現的API版本建構一個Swagger通路位址。
現在,讓我們運作程式并檢視代碼的結果。讓我們看看Swagger UI,WeatherForecast的測試版本1 API和版本2 的API,看看如果我們發送請求,它們是否正常工作。您可以在下面的截圖中看到效果,我們可以選擇要檢查的API版本:
我們可以看到v1和v2的WeatherForecast接口是不一樣的,v1的版本被抛棄了,是以顯示成灰色的。
而v2版本是正常的:
我們可以随便傳入一個City參數,然後就可以看到傳回記錄了: