.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,如下所示:
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://appstv6elnt7382.h5.xiaoeknow.com/")
        }
    };
 
    if (description.IsDeprecated)
        info.Description += " <strong>該API版本已經過期.</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(
            $"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
    }
});

以上通過循環爲每個發現的API版本構建一個Swagger訪問地址。
現在,讓我們運行程序並查看代碼的結果。讓我們看看Swagger UI,WeatherForecast的測試版本1 API和版本2 的API,看看如果我們發送請求,它們是否正常工作。您可以在下面的截圖中看到效果,我們可以選擇要檢查的API版本:

我們可以看到v1和v2的WeatherForecast接口是不一樣的,v1的版本被拋棄了,所以顯示成灰色的。

而v2版本是正常的:

我們可以隨便傳入一個City參數,然後就可以看到返回記錄了:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章