天天看點

.NET 6當中的Web API版本控制

作者:IT人張飛洪
大家好,我是張飛洪,感謝您的閱讀,我會不定期和你分享學習心得,希望我的文章能成為你成長路上的墊腳石,讓我們一起精進。

為了了解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版本:

.NET 6當中的Web API版本控制

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

.NET 6當中的Web API版本控制

而v2版本是正常的:

.NET 6當中的Web API版本控制

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

.NET 6當中的Web API版本控制

繼續閱讀