【问题标题】:ASP.NET API version rangesASP.NET API 版本范围
【发布时间】:2021-11-20 21:13:34
【问题描述】:

我们的产品是一个client/server 应用程序,它在现场拥有多个版本的客户端,但只有一台运行最新版本的服务器来为所有API 调用提供服务。我们有/将有数百个API 端点,我正在尝试如何最好地处理版本控制。我想做的是能够避免将attributes 应用于每个方法的繁重任务,或者每次我们进行微小更改时复制整个控制器。

我可能会误解大多数关于此的文档/实践,但似乎每次您碰到API 时,您都必须经历并完成所有这些工作,这似乎充其量是低效的。

相反,我想做的是将attribute 应用到每个端点,并使用编写时的版本,然后客户端会找到最接近的版本等于或小于客户端版本

例如,如果端点被写入[ApiVersion("1.0")],那么这就是它获得的属性。如果我们必须修改它,我会复制该方法,重命名它,应用RoutePrefix attribute 以便正确命中并使用我们整个API 的版本应用一个新的attribute(在这个例子中我输入了1.5)。

这是一个简单的例子:

[HttpGet]
[ApiVersion("1.0")]
[Route("GetHeartBeat")]
public bool GetHeartBeat()
{
    return true;
}

[HttpGet]
[ApiVersion("1.5")]
[Route("GetHeartBeat")]
public bool GetHeartBeat2()
{
    return false;
}

当我使用 url 版本控制时,这没有问题:

  • /api/v1.0/GetHeartBeat
  • /api/v1.5/GetHeartBeat

/api/v1.3/GetHeartBeat 不存在,因为该版本不存在..

我想要发生的是,如果我有一个运行1.3 的客户端,那么它将找到等于或小于最新版本的最接近的版本。所以/api/v1.3/GetHeartBeat 会被接收,因为1.3 不存在,那么它会查看最接近/更早的版本,在这种情况下是1.0

我可以编写一堆路由逻辑来完成这个,但我觉得必须有一个开箱即用的解决方案,因为我不能成为第一个尝试这个的人。有没有一个nuget 包可以实现这个?

【问题讨论】:

  • 我不认为一个属性对你有任何好处,但是一个中间件可能会有所帮助,如果你用 IActionDescriptorCollectionProvider 注入它,你可以检查一个路由是否存在,然后降级直到你找到一个匹配的路由

标签: c# asp.net api-versioning aspnet-api-versioning


【解决方案1】:

您实际上是在问两个问题。如何在服务器端映射事物是一个实现细节,有很多选择。属性不是应用 API 版本控制元数据的硬性要求。您可以使用约定,包括您自己的约定。 API 版本必须是离散的。这是设计使然。 API 版本更像是一种媒体类型。您不能随意添加媒体类型,也不能添加 API 版本,并且必须期望客户端能够理解它。

由于您拥有双方,因此您有一些很好的途径可以让事情按照您想要的方式进行。服务器应该从不假设客户端想要什么,并且客户端应该总是必须明确地询问服务器它想要什么。实现目标的最简单方法是协商 API 版本。好,很好。怎么样?

我怀疑今天没有很多人这样做,但 API 版本控制在很早的时候就包含了必要的机制来实现这一点。有很多用例,但最常见的是工具(例如:客户端代码生成)和客户端版本协商。第一步是启用报告 API 版本:

services.AddApiVersioning(options => options.ReportApiVersions = true);

您还可以将[ReportApiVersions] 应用于特定的控制器操作

这将允许通过api-supported-versionsapi-deprecated-versions HTTP 标头报告可用的 API 版本。请记住,deprecated 并不意味着它不存在,它只是意味着它会在某个时候消失;你控制政策。您的客户可以使用此信息来记录有关过时版本的警告,或者它会影响您的客户选择合适版本的决定。

您面临的部分挑战是按 URL 段进行版本控制。是的,它非常流行,但它违反了 统一接口 约束。 v1.api.com 是一个端点v1.0/GetHeartBeatv1.5/GetHeartBeat标识符。这两个已识别的资源几乎可以肯定不是不同的资源,而是具有不同的表示。为什么这很重要?更改每个版本的标识符(例如 URL)会导致客户端移动目标。其他所有版本控制方法都将始终使用GetHeartBeat。我敢肯定,你做改变的路太远了,但这会导致解决方案。

您使用哪个控制器实现并不重要,但您基本上需要一个执行以下操作的操作:

[ApiController]
[Route("api/[controller]")]
public class GetHeartBeatController : ControllerBase
{
    [ReportApiVersions] // ← instead of ApiVersioningOptions.ReportApiVersions = true
    [ApiVersionNeutral] // ← allow any and all versions, including none at all
    [HttpOptions]
    public IActionResult Options()
    {
        // Allow is required by spec; you may need addition information
        Response.Headers.Add("Allow", new StringValues(new[]{"GET", "OPTIONS"}));
        Response.GetTypedHeaders().CacheControl = new()
        {
            MaxAge = TimeSpan.FromDays(1d),
        };
        return Ok();
    }
}

现在,如果您的客户发送:

OPTIONS api/getheartbeat HTTP/2
Host: localhost

你会得到类似的东西:

HTTP/2 200 OK
Cache-Control: max-age=86400
Api-Supported-Versions: 1.0, 1.5

如果您的客户端正在运行 1.3,它现在具有从列表中选择 1.0 作为最合适的 API 版本所需的知识。 Cache-Control 标头可以用作服务器告诉客户端它可以缓存结果多长时间(但不是必须)的一种方式。我认为 API 版本的更改频率不会超过每天一次,因此这似乎是一种合理的方法。

你没有提到你有什么类型的客户。如果它是基于浏览器的客户端,您可能需要对此设置进行一些额外的工作,以使其与 CORS 配合使用(如果需要)。或者,您可以使用HEAD 方法获得相同的结果。我认为OPTIONS 更合适,但如果您遇到任何复杂情况,您可能会发现让它与 CORS 一起玩是不值得的。

【讨论】:

    猜你喜欢
    • 2013-09-22
    • 1970-01-01
    • 1970-01-01
    • 2018-06-07
    • 2012-01-11
    • 2016-04-21
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    相关资源
    最近更新 更多