使用 ASP.NET Core实现REST API 版本控制
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
RESTful API 版本控制的最佳方法一直备受争议,每种方法都有其自身的优缺点。在 REST API 版本控制方面,并不存在“一劳永逸”的解决方案。
本文将讨论一些常用的 API 版本控制策略,并演示如何在 ASP.NET Core Web API 中实现它们。
API 版本控制的挑战
最符合 RESTful 原则的 API 版本控制方式是完全不进行版本控制。然而,随着时间的推移,需求会发生变化,API 也应该相应地进行演进以适应这些变化。因此,在发布 API 的第一个版本之前,必须预见到这些变化并考虑版本控制策略。一旦 API 发布,任何未来的更改都不应破坏使用该 API 的现有客户端应用程序。
在典型的项目中,例如类库或可执行程序,我们可以通过创建不同版本的包来实现版本控制,通常是通过更改程序集版本来实现。API 版本控制的难点在于需要同时支持多个 API 版本。旧客户端可能仍然依赖于旧版本,而新客户端则希望使用最新的 API。在服务器上并排部署多个 API 版本既不切实际也不方便。REST API 的版本控制方法是在同一代码库中支持多个版本。
.NET Core 中的 REST API 版本控制
从零开始在 .NET Core Web API 中实现版本控制策略可能颇具挑战性。微软提供了一个名为Microsoft.AspNetCore.Mvc.Versioning的 NuGet 包,旨在简化 .NET Core REST API 的版本控制流程。
入门
创建一个新的 .NET Core Web API 项目。将Microsoft.AspNetCore.Mvc.Versioning NuGet 包作为依赖项添加到该项目,并将 API Versioning 包添加到该项目的服务容器中。
// startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add API Versioning to the service container to your project
services.AddApiVersioning();
}
配置 API 版本控制的默认行为
如果向 values 资源(/api/values 端点)发出 GET 请求,则会返回 404 Bad Request 响应。这是因为 API 版本控制包的默认行为要求在所有情况下都指定 API 版本。
GET /api/values
{
"error": {
"code": "ApiVersionUnspecified",
"message": "An API version is required, but was not
specified.",
"innerError": null
}
}
使用 lambda 函数配置 API 版本控制,并指定默认 API 版本号,在未指定 API 版本时使用该默认 API 版本号。
// startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add API Versioning to as service to your project
services.AddApiVersioning(config =>
{
// Specify the default API Version as 1.0
config.DefaultApiVersion = new ApiVersion(1, 0);
// If the client hasn't specified the API version in the request, use the default API version number
config.AssumeDefaultVersionWhenUnspecified = true;
});
}
如果向 values 资源(api/values)发出请求时未指定 API 版本,服务器将响应 200 OK。由于请求中未指定 API 版本,因此假定为默认版本 1.0。此外,values 控制器也未指定版本号,因此也假定为默认版本。
向客户端通报已接受的 API 版本
配置 API 版本控制时,将ReportApiVersion属性设置为 true,以便让用户了解支持的 API 版本。
// startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add API Versioning to as service to your project
services.AddApiVersioning(config =>
{
// Specify the default API Version
config.DefaultApiVersion = new ApiVersion(1, 0);
// If the client hasn't specified the API version in the request, use the default API version number
config.AssumeDefaultVersionWhenUnspecified = true;
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
});
}
现在,向 values 端点发送 GET 请求将返回 200 OK,并包含名为api-supported-versions 的响应头,其中列出了该端点的所有可用 API 版本。
为 API 标明支持的版本对 API 使用者来说非常有用。使用者可以读取api-supported-versions标头,从而了解该特定端点支持哪些版本。
版本特定的控制器和操作
当客户端请求特定版本的 API 端点时,该请求应重定向到处理所请求 API 版本的相应控制器或操作。有多种方法可以分配控制器和操作来处理特定版本的请求。可以为每个 API 版本创建单独的控制器,并根据请求的 API 版本将请求定向到特定的控制器。然而,本文将演示如何在单个控制器中创建所有特定版本的操作。
第一步是使用ApiVersion属性指定控制器支持的 API 版本。以下代码片段指定并声明控制器接受 API v1.0 和 v1.1 版本。目前尚未创建基于 API 版本处理请求的单独操作。
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace apiVersioningDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "value";
}
}
}
由于 values 控制器中的操作未指定版本号,因此假定所有端点的默认版本均为 1.0。
为 GET values 端点添加一个新的操作方法,以使用MapToApiVersion属性处理 API 版本 1.1。
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace apiVersioningDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values
[HttpGet]
[MapToApiVersion("1.1")] // v1.1 specific action for GET api/values endpoint
public ActionResult<IEnumerable<string>> GetV1_1()
{
return new string[] { "version 1.1 value 1", "version 1.1 value2 " };
}
}
}
在上面的代码片段中,对 v1.0 的 GET 请求由 Get() 操作处理,而对 v1.1 的 GET 请求由 GetV1_1() 操作处理。
版本控制策略
既然 API 支持多个版本,我们需要一种方法让客户端能够指定他们请求的 API 版本。目前有几种不同的方法可以让客户端在发出请求时发送版本信息。下面将讨论其中的一些策略:
使用查询参数
Microsoft.AspNetCore.Mvc.Versioning 包提供的默认版本控制方案使用查询参数api-version。
使用查询字符串对 API 进行版本控制,允许客户端根据自身需求显式指定版本号。与其他版本控制策略不同,客户端无需在每个请求中都包含 API 版本信息。如果客户端未指定 `api-version` 查询字符串,则会隐式请求默认版本。
https://demo.org/api/resource?api-version=1.1
使用请求头
另一种 API 版本控制方法是使用请求头,请求头的值用于指定 API 版本。许多开发者推崇这种方法,因为与 URL 路径参数和查询字符串方法不同,使用请求头无需在客户端修改 URL。使用请求头进行版本控制的缺点是,客户端无法立即看到版本控制选项。
默认情况下,Microsoft.AspNetCore.Mvc.Versioning包使用查询参数策略在请求中指定 API 版本。要配置 API 版本控制,可以使用版本读取器将请求头作为版本控制策略。ApiVersionReader用于指定版本控制方案,此类会分析请求并确定请求的版本。如果未指定,则默认的版本读取器方案是QueryStringApiVersionReader,这就是为什么我们之前能够使用 api-version 查询参数请求 v1.1 的原因。
将版本读取器更改为HeaderApiVersionReader后,客户端可以使用请求头而非查询参数发送版本信息。客户端可以在X-version 请求头中指定 API 版本。请注意,传递给HeaderApiVersionReader()方法的参数“X-version”字符串是为用于发送 API 版本信息的请求头选择的任意名称。
// startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add API Versioning to as service to your project
services.AddApiVersioning(config =>
{
// Specify the default API Version
config.DefaultApiVersion = new ApiVersion(1, 0);
// If the client hasn't specified the API version in the request, use the default API version number
config.AssumeDefaultVersionWhenUnspecified = true;
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
// DEFAULT Version reader is QueryStringApiVersionReader();
// clients request the specific version using the X-version header
config.ApiVersionReader = new HeaderApiVersionReader("X-version");
});
}
使用媒体类型(Accept 标头)版本控制
另一种对 API 进行版本控制的方法是利用 HTTP 提供的内容协商机制。当客户端使用 Accept 请求头请求资源时,它们可以显式地在媒体类型中包含版本号。
在对下面所示的 values 端点发出的 GET 请求中,客户端明确表示它接受服务器返回的媒体类型为 application/json、版本号为 1.1 的响应。
GET /values
Accept: application/json;v=1.1
服务器可以从请求中读取 Accept 标头,并以相应的 API 版本进行响应。
使用媒体类型版本控制方案的一个缺点是,它可能相当晦涩难懂,难以实现,而且客户端不会立即意识到他们可以使用 Accept 标头请求不同的 API 版本。
要使用媒体类型实现版本控制,请将ApiVersionReader设置为MediaTypeApiVersionReader类的实例。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add API Versioning to as service to your project
services.AddApiVersioning(config =>
{
// Specify the default API Version
config.DefaultApiVersion = new ApiVersion(1, 0);
// If the client hasn't specified the API version in the request, use the default API version number
config.AssumeDefaultVersionWhenUnspecified = true;
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
// Versioning using media type
config.ApiVersionReader = new MediaTypeApiVersionReader("v");
});
}
使用 URL 路径版本控制方案
直接在 URL 路径中使用版本号是 API 版本控制最简单的方法之一。URL 路径版本控制方式更加直观,因为它在 URL 本身就明确地包含了版本号。然而,这种方法要求客户端在每次 API 版本更新时都必须更改其应用程序中的 URL。此外,将 API 版本嵌入 URL 本身会违反 REST API 的一个基本原则,即每个 URL 都应该代表一个特定的资源,并且该资源的 URL 不应随时间改变。这种方法更适用于每个新 API 版本都包含重大变更的情况。
https://demo.org/api/v2/resource
要实现 URL 路径版本控制,请修改控制器的 Route 属性,使其在路径参数中接受 API 版本信息。
Route 属性已更改为:
[Route("api/v{version:apiVersion}/[controller]")]
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace apiVersioningDemo.Controllers
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("1.1")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values
[HttpGet]
[MapToApiVersion("1.1")] // v1.1 specific action for GET api/values endpoint
public ActionResult<IEnumerable<string>> GetV1_1()
{
return new string[] { "version 1.1 value 1", "version 1.1 value2 " };
}
}
}
支持多种版本控制方案
支持多种 API 版本控制方案提供了灵活性,并允许客户端选择他们想要的版本控制方案。以下代码片段演示了如何使用 ApiVersionReader类中的静态方法Combine同时支持查询参数版本控制方案和请求头版本控制方案。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// Add API Versioning to as service to your project
services.AddApiVersioning(config =>
{
// Specify the default API Version
config.DefaultApiVersion = new ApiVersion(1, 0);
// If the client hasn't specified the API version in the request, use the default API version number
config.AssumeDefaultVersionWhenUnspecified = true;
// Advertise the API versions supported for the particular endpoint
config.ReportApiVersions = true;
// DEFAULT Version reader is QueryStringApiVersionReader();
// clients request the specific version using the X-version header
//config.ApiVersionReader = new HeaderApiVersionReader("X-version");
// Supporting multiple versioning scheme
config.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("X-version"), new QueryStringApiVersionReader("api-version"));
});
}
现在 API 支持两种不同的版本控制方案,客户端应确保仅使用其中一种受支持的方案。理论上,客户端可以在请求中使用两种方案来指定 API 版本,只要它们指定的版本号相同即可。如果在同一个请求中使用不同的 API 版本控制方案请求两个不同的版本,则会导致请求错误。
在上面的示例中,使用查询参数方案请求 API v1.0,使用标头方案请求 v1.1,这导致了 API 版本不明确错误。
宣传已弃用的版本
与声明端点支持的 API 版本类似,也可以通过将ApiVersion属性中的 deprecated 属性设置为 true 来声明即将弃用的 API 版本。客户端可以读取响应头中的api-deprecated-versions 信息,从而识别已弃用的 API 版本。
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
namespace apiVersioningDemo.Controllers
{
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
// DEPRECATING an API Version
[ApiVersion("1.0", Deprecated = true)]
[ApiVersion("1.1")]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values
[HttpGet]
[MapToApiVersion("1.1")] // v1.1 specific action for GET api/values endpoint
public ActionResult<IEnumerable<string>> GetV1_1()
{
return new string[] { "version 1.1 value 1", "version 1.1 value2 " };
}
}
}
结论
本文探讨了 .NET Core 中 REST API 的多种版本控制方法。根据具体的使用场景和 API 的使用者,可以选择合适的版本控制策略。
文章来源:https://dev.to/99darshan/restful-web-api-versioning-with-asp-net-core-1e8g








