Dotnet Core多版本API共存的優(yōu)雅實現(xiàn)
API升級,新舊版本的API共存,怎么管理呢?
?
一、前言
最近,單位APP做了升級,同步的,API也做了升級。
升級過程中,出現(xiàn)了一點問題:API升級后,舊API也需要保留,因為有舊的APP還在使用中。
那么,API端如何作到多個版本共存呢?
二、快速的解決辦法
API的露出,是在API的Route定義中實現(xiàn)的。看下面的例子:
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[Route("demo")]
public ActionResult
DemoFunc()
{
}
}
那我們知道,這個API的終結(jié)點是:/api/demo/demo。代碼中[controller]是個可替換變量,編譯時會替換為當前控制器的名稱。
這個Route,里面的參數(shù)是個字符串,也就是說是可以隨便換的。所以,對于多版本API,有個快速的辦法,就是在里面做文章。
我們可以寫成:
[Route("api/v1/[controller]")]
public class DemoController : ControllerBase
{
[Route("demo")]
public ActionResult
DemoFunc()
{
}
}
或者
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[Route("v1/demo")]
public ActionResult
DemoFunc()
{
}
}
這樣就區(qū)分出了版本號。
?
當然,這樣做比較LOW,因為版本號是硬編碼在代碼中的。而且,這個改動會影響到API的終結(jié)點,例如上面兩個變化,會讓終結(jié)點變?yōu)椋?code style="font-size: inherit;line-height: inherit;word-wrap: break-word;padding: 2px 4px;border-top-left-radius: 4px;border-top-right-radius: 4px;border-bottom-right-radius: 4px;border-bottom-left-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(233, 105, 0);background-color: rgb(248, 248, 248);">/api/v1/demo/demo和/api/demo/v1/demo。如果前端可以方便修改,也算是一個方法。但對于我們APP已經(jīng)上線運行來說,這個改動無法接受。
三、優(yōu)雅的解決辦法
這個方案,才是今天要說的核心內(nèi)容。
?
首先,我們需要從Nuget上引入兩個庫:
% dotnet add package Microsoft.AspNetCore.Mvc.Versioning
% dotnet add package Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer
這兩個庫,Versioning用來實現(xiàn)API的版本控制,Versioning.ApiExplorer用來實現(xiàn)元數(shù)據(jù)的發(fā)現(xiàn)工作。
引入完成后,修改Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
services.AddControllers();
}
就可以了。
這里面用了兩個配置:AddApiVersioning,主要用來配置向前兼容,定義了如果沒有帶版本號的訪問,會默認訪問v1.0的接口。AddVersionedApiExplorer用來添加API的版本管理,并定義了版本號的格式化方式,以及兼容終結(jié)點上帶版本號的方式。
到這兒,引入版本管理的工作就完成了。
?
使用時,就直接在控制器或方法上定義版本號:
[ApiVersion("1")]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
[MapToApiVersion("2")]
[Route("demo")]
public ActionResult
DemoFunc()
{
}
}
這里面,又是兩個屬性:ApiVersion定義控制器提供哪個版本的API。這個屬性可以定義多個。例如,我們控制器里既有v1的API,也有v2的API,我們可以寫成:
[ApiVersion("1")]
[ApiVersion("2")]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
}
MapToApiVersion是API的版本定義,定義我們這個API是哪一個版本。
方法就這么簡單。其它的,微軟都幫我們做好了。
?
那,通常我們會用Swagger來做API文檔。這個方法如何跟Swagger配合呢?
四、與Swagger的配合
Swagger也來自于Nuget的引用:
% dotnet add package swashbuckle.aspnetcore
引用后,通常我們Startup.cs里的配置是這樣的:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo", Version = "V1" });
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(option =>
{
option.SwaggerEndpoint("/swagger/v1/swagger.json", "Demo");
});
}
?
API多版本管理與Swagger配合,也有一個快速但比較LOW的方法:
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(option =>
{
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo", Version = "V1" });
option.SwaggerDoc("v1", new OpenApiInfo { Title = "Demo", Version = "V2" });
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(option =>
{
option.SwaggerEndpoint("/swagger/v1/swagger.json", "Demo V1");
option.SwaggerEndpoint("/swagger/v2/swagger.json", "Demo V2");
});
}
這個方法也可以快速實現(xiàn),不過跟上邊的情況一樣,版本號是硬編碼的。
?
其實,也有另一個比較優(yōu)雅的方式,就是手動實現(xiàn)IConfigureOptions
和過濾IOperationFilter。
先看Startup.cs里:
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient
, ConfigureSwaggerOptions>();
services.AddSwaggerGen(options => options.OperationFilter
());
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseSwagger();
app.UseSwaggerUI(option =>
{
foreach (var description in provider.ApiVersionDescriptions)
{
c.SwaggerEndpoint($
"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
}
});
}
這里加了兩個類,第一個ConfigureSwaggerOptions:
internal class ConfigureSwaggerOptions : IConfigureOptions
{
private readonly IApiVersionDescriptionProvider _provider;
public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) => _provider = provider;
public void Configure(SwaggerGenOptions options)
{
foreach (var description in _provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}
private OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info =
new OpenApiInfo()
{
Title =
"Demo API",
Version = description.ApiVersion.ToString(),
};
if (description.IsDeprecated)
{
info.Description +=
" 方法被棄用.";
}
return info;
}
}
第二個SwaggerDefaultValues:
internal class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
if (operation.Parameters == null)
return;
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
if (parameter.Description == null)
{
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;
}
}
}
代碼不一行行解釋了,都是比較簡單的。
?
運行,進入Swagger界面,右上角Select a definition,可以選擇我們定義的版本號。?
今天的配套代碼已上傳到Github,位置在:https://github.com/humornif/Demo-Code/tree/master/0035/demo
再見,VIP,臥槽又來一個看片神器!
你應(yīng)該知道.NET Core的8點總結(jié)!
