发布于 2026-01-06 0 阅读
0

使用 ASP.NET Core 进行 REST API 版本控制 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 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();
}
Enter fullscreen mode Exit fullscreen mode

配置 API 版本控制的默认行为

如果向 values 资源(/api/values 端点)发出 GET 请求,则会返回 404 Bad Request 响应。这是因为 API 版本控制包的默认行为要求在所有情况下都指定 API 版本。

GET /api/values
Enter fullscreen mode Exit fullscreen mode
{
    "error": {
        "code": "ApiVersionUnspecified",
        "message": "An API version is required, but was not     
specified.",
        "innerError": null
    }
}
Enter fullscreen mode Exit fullscreen mode

使用 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;
    });
}
Enter fullscreen mode Exit fullscreen mode

如果向 values 资源(api/values)发出请求时未指定 API 版本,服务器将响应 200 OK。由于请求中未指定 API 版本,因此假定为默认版本 1.0。此外,values 控制器也未指定版本号,因此也假定为默认版本。

NO-VERSION-INFO.PNG

向客户端通报已接受的 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;
    });
}
Enter fullscreen mode Exit fullscreen mode

现在,向 values 端点发送 GET 请求将返回 200 OK,并包含名为api-supported-versions 的响应头,其中列出了该端点的所有可用 API 版本。

为 API 标明支持的版本对 API 使用者来说非常有用。使用者可以读取api-supported-versions标头,从而了解该特定端点支持哪些版本。

报告 API 版本

版本特定的控制器和操作

当客户端请求特定版本的 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";
        }
   }
}
Enter fullscreen mode Exit fullscreen mode

控制器中的 API 版本属性

由于 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 " };
        }
  }
}
Enter fullscreen mode Exit fullscreen mode

在上面的代码片段中,对 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
Enter fullscreen mode Exit fullscreen mode

查询字符串版本控制

使用请求头

另一种 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");
            });
        }
Enter fullscreen mode Exit fullscreen mode

请求头版本控制

使用媒体类型(Accept 标头)版本控制

另一种对 API 进行版本控制的方法是利用 HTTP 提供的内容协商机制。当客户端使用 Accept 请求头请求资源时,它们可以显式地在媒体类型中包含版本号。

在对下面所示的 values 端点发出的 GET 请求中,客户端明确表示它接受服务器返回的媒体类型为 application/json、版本号为 1.1 的响应。

GET /values
Accept: application/json;v=1.1
Enter fullscreen mode Exit fullscreen mode

服务器可以从请求中读取 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");

            });
}
Enter fullscreen mode Exit fullscreen mode

媒体类型接受标头版本控制

使用 URL 路径版本控制方案

直接在 URL 路径中使用版本号是 API 版本控制最简单的方法之一。URL 路径版本控制方式更加直观,因为它在 URL 本身就明确地包含了版本号。然而,这种方法要求客户端在每次 API 版本更新时都必须更改其应用程序中的 URL。此外,将 API 版本嵌入 URL 本身会违反 REST API 的一个基本原则,即每个 URL 都应该代表一个特定的资源,并且该资源的 URL 不应随时间改变。这种方法更适用于每个新 API 版本都包含重大变更的情况。

https://demo.org/api/v2/resource
Enter fullscreen mode Exit fullscreen mode

要实现 URL 路径版本控制,请修改控制器的 Route 属性,使其在路径参数中接受 API 版本信息。

Route 属性已更改为:

[Route("api/v{version:apiVersion}/[controller]")]
Enter fullscreen mode Exit fullscreen mode
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 " };
        }
  }
}
Enter fullscreen mode Exit fullscreen mode

路径参数版本控制

支持多种版本控制方案

支持多种 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"));
            });
        }
Enter fullscreen mode Exit fullscreen mode

现在 API 支持两种不同的版本控制方案,客户端应确保仅使用其中一种受支持的方案。理论上,客户端可以在请求中使用两种方案来指定 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 " };
        }
  }
}
Enter fullscreen mode Exit fullscreen mode

已弃用的 API 版本

结论

本文探讨了 .NET Core 中 REST API 的多种版本控制方法。根据具体的使用场景和 API 的使用者,可以选择合适的版本控制策略。

文章来源:https://dev.to/99darshan/restful-web-api-versioning-with-asp-net-core-1e8g