.NET Core WebAPI 后备 API 版本,以防缺少次要版本

2024-01-12

经过多次尝试和阅读文章后,我决定将我的问题放在这里。我想要的是:我正在研究应用程序的 api 版本控制。 .NET Core 支持的版本格式(Microsoft.AspNetCore.Mvc.Versioningpackage) 是 Major.Minor,这就是我想在我从事的项目中使用的。我想要的是一个后备版本,以防客户端未指定次要版本。 我正在使用 .NET core 2.2,并使用api-version标头中指定。相应的 API 版本控制配置如下所示:

    services.AddApiVersioning(options => { 
        options.ReportApiVersions = true;
        options.ApiVersionReader = new HeaderApiVersionReader("api-version");
        options.ErrorResponses = new ApiVersioningErrorResponseProvider();
    });

我每个版本都有以下两个控制器:(为了解决这个问题,控制器被简化了):

[ApiVersion("1.0")]  
[Route("api/[controller]")]  
public class ValueControllerV10 : Controller  
{  
    [HttpGet(Name = "collect")]  
    public String Collect()  
    {  
        return "Version 1.0";  
    }  
} 


[ApiVersion("1.1")]  
[Route("api/[controller]")]  
public class ValueControllerV11 : Controller  
{  
    [HttpGet(Name = "collect")]  
    public String Collect()  
    {  
        return "Version 1.1";  
    }  
}  

如果客户指定api-version=1.0然后使用 ValueControllerV10。当然,如果客户指定api-version=1.1,然后按预期使用 ValueControllerV11。

现在我的问题来了。如果客户指定api-version=1(因此只有主要版本而没有次要版本),则使用ValueControllerV10。这是因为ApiVersion.Parse("1")等于ApiVersion.Parse("1.0"), 如果我没有记错的话。但在这种情况下我想要的是调用给定主要版本的最新版本,在我的示例中为 1.1。

我的尝试:

First:指定[ApiVersion("1")] at ValueControllerV11

    [ApiVersion("1")]  
    [ApiVersion("1.1")]  
    [Route("api/[controller]")]  
    public class ValueControllerV11 : Controller  
    {  
        [HttpGet(Name = "collect")]  
        public String Collect()  
        {  
            return "Version 1.1";  
        }  
    }  

它不起作用,它导致

AmbiguousMatchException: The request matched multiple endpoints

为了解决这个问题,我想出了第二种方法:

Second:使用自定义IActionConstraint。为此,我遵循了这些文章:

  • https://stevenknox.net/aspnet-core-mvc-action-priority-using-actionconstraints/ https://stevenknox.net/aspnet-core-mvc-action-priority-using-actionconstraints/
  • https://www.strathweb.com/2017/06/using-iactionconstraints-in-asp-net-core-mvc/ https://www.strathweb.com/2017/06/using-iactionconstraints-in-asp-net-core-mvc/

然后我创建了以下类:

[AttributeUsage(AttributeTargets.Method)]
public class HttpRequestPriority : Attribute, IActionConstraint
{
    public int Order
    {
        get
        {
            return 0;
        }
    }

    public bool Accept(ActionConstraintContext context)
    {
        var requestedApiVersion = context.RouteContext.HttpContext.GetRequestedApiVersion();

        if (requestedApiVersion.MajorVersion.Equals(1) && !requestedApiVersion.MinorVersion.HasValue)
        {
            return true;
        }

        return false;
    }
}

并用于ValueControllerV11:

[ApiVersion("1")]  
[ApiVersion("1.1")]  
[Route("api/[controller]")]  
public class ValueControllerV11 : Controller  
{  
    [HttpGet(Name = "collect")]
    [HttpRequestPriority]  
    public String Collect()  
    {  
        return "Version 1.1";  
    }  
}

嗯,它解决了AmbiguousMatchException,但会覆盖默认行为Microsoft.AspNetCore.Mvc.Versioning打包所以如果客户端使用api-version 1.1,然后她得到 404 Not Found 返回,根据执行情况这是可以理解的HttpRequestPriority

Third: Using MapSpaFallbackRoute in Startup.cs, 有条件的:

        app.MapWhen(x => x.GetRequestedApiVersion().Equals("1") && x.GetRequestedApiVersion().MinorVersion == null, builder =>
        {
            builder.UseMvc(routes =>
            {
                routes.MapSpaFallbackRoute(
                    name: "spa-fallback",
                    defaults: new {controller = nameof(ValueControllerV11), action = "Collect"});
            });
        });

        app.UseMvc();

也不起作用,没有任何影响。名字MapSpaFallbackRoute也给我一种感觉,这不是我需要使用的......

所以我的问题是:当未指定次要版本时,如何引入后备“使用最新”行为api-version?提前致谢!


这本质上不支持开箱即用。浮动版本、范围等与 API 版本控制的原则相悖。 API 版本并不也不可能暗示任何向后兼容性。除非您在封闭系统中控制双方,否则假设客户可以处理任何合同变更,即使您只添加一个新成员,也是一个谬论。最终,如果客户端请求 1/1.0,那么这就是他们应该得到的,否则服务器应该说它不受支持。

抛开我的观点不谈,有些人仍然想要这种行为。这不是特别简单,但您应该能够使用自定义来实现您的目标IApi版本路由策略 https://github.com/microsoft/aspnet-api-versioning/blob/master/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/IApiVersionRoutePolicy.cs或自定义端点匹配器 - 这取决于您使用的路由样式。

如果您仍在使用legacy路由,这可能是最简单的,因为您只需创建一个新策略或扩展现有策略默认Api版本路由策略通过覆盖单场比赛 https://github.com/microsoft/aspnet-api-versioning/blob/master/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/DefaultApiVersionRoutePolicy.cs#L103并将其注册到您的服务配置中。您会知道这就是您正在寻找的场景,因为传入的 API 版本不会有次要版本。你是对的1 and 1.0将等同于相同,但次要版本不合并;所以,ApiVersion.MinorVersionnull在这种情况下。

如果您正在使用端点路由,您需要更换Api版本匹配器策略 https://github.com/microsoft/aspnet-api-versioning/blob/master/src/Microsoft.AspNetCore.Mvc.Versioning/Routing/ApiVersionMatcherPolicy.cs。以下内容应该接近您想要实现的目标:

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Microsoft.AspNetCore.Mvc.Versioning.ApiVersionMapping;

public sealed class MinorApiVersionMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
{
    public MinorApiVersionMatcherPolicy(
        IOptions<ApiVersioningOptions> options,
        IReportApiVersions reportApiVersions,
        ILoggerFactory loggerFactory )
    {
        DefaultMatcherPolicy = new ApiVersionMatcherPolicy(
            options, 
            reportApiVersions, 
            loggerFactory );
        Order = DefaultMatcherPolicy.Order;
    }

    private ApiVersionMatcherPolicy DefaultMatcherPolicy { get; }

    public override int Order { get; }

    public bool AppliesToEndpoints( IReadOnlyList<Endpoint> endpoints ) =>
        DefaultMatcherPolicy.AppliesToEndpoints( endpoints );

    public async Task ApplyAsync(
        HttpContext httpContext,
        EndpointSelectorContext context,
        CandidateSet candidates )
    {
        var requestedApiVersion = httpContext.GetRequestedApiVersion();
        var highestApiVersion = default( ApiVersion );
        var explicitIndex = -1;
        var implicitIndex = -1;

        // evaluate the default policy
        await DefaultMatcherPolicy.ApplyAsync( httpContext, context, candidates );

        if ( requestedApiVersion.MinorVersion.HasValue )
        {
            // we're done because a minor version was specified
            return;
        }

        var majorVersion = requestedApiVersion.MajorVersion;

        for ( var i = 0; i < candidates.Count; i++ )
        {
            // make all candidates invalid by default
            candidates.SetValidity( i, false );

            var candidate = candidates[i];
            var action = candidate.Endpoint.Metadata?.GetMetadata<ActionDescriptor>();

            if ( action == null )
            {
                continue;
            }

            var model = action.GetApiVersionModel( Explicit | Implicit );
            var maxApiVersion = model.DeclaredApiVersions
                                        .Where( v => v.MajorVersion == majorVersion )
                                        .Max();

            // remember the candidate with the next highest api version
            if ( highestApiVersion == null || maxApiVersion >= highestApiVersion )
            {
                highestApiVersion = maxApiVersion;

                switch ( action.MappingTo( maxApiVersion ) )
                {
                    case Explicit:
                        explicitIndex = i;
                        break;
                    case Implicit:
                        implicitIndex = i;
                        break;
                }
            }
        }

        if ( explicitIndex < 0 && ( explicitIndex = implicitIndex ) < 0 )
        {
            return;
        }

        var feature = httpContext.Features.Get<IApiVersioningFeature>();

        // if there's a match:
        //
        // 1. make the candidate valid
        // 2. clear any existing endpoint (ex: 400 response)
        // 3. set the requested api version to the resolved value
        candidates.SetValidity( explicitIndex, true );
        context.Endpoint = null;
        feature.RequestedApiVersion = highestApiVersion;
    }
}

然后您需要像这样更新服务配置:

// IMPORTANT: must be configured after AddApiVersioning
services.Remove( services.Single( s => s.ImplementationType == typeof( ApiVersionMatcherPolicy ) ) );
services.TryAddEnumerable( ServiceDescriptor.Singleton<MatcherPolicy, MinorApiVersionMatcherPolicy>() );

如果我们考虑这样的控制器:

[ApiController]
[ApiVersion( "2.0" )]
[ApiVersion( "2.1" )]
[ApiVersion( "2.2" )]
[Route( "api/values" )]
public class Values2Controller : ControllerBase
{
    [HttpGet]
    public string Get( ApiVersion apiVersion ) =>
        $"Controller = {GetType().Name}\nVersion = {apiVersion}";

    [HttpGet]
    [MapToApiVersion( "2.1" )]
    public string Get2_1( ApiVersion apiVersion ) =>
        $"Controller = {GetType().Name}\nVersion = {apiVersion}";

    [HttpGet]
    [MapToApiVersion( "2.2" )]
    public string Get2_2( ApiVersion apiVersion ) =>
        $"Controller = {GetType().Name}\nVersion = {apiVersion}";
}

当您请求时api/values?api-version=2,你会匹配2.2.

我要重申,这通常不是一个好主意,因为客户应该能够依赖稳定的版本。使用status如果您想要的话,该版本可能更合适预发布API(例如:2.0-beta1).

我希望这有帮助。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.NET Core WebAPI 后备 API 版本,以防缺少次要版本 的相关文章

随机推荐

  • 如何使用 docker-py (docker SDK) 将文件从主机复制到容器

    我知道 有可能使用以下方法在主机和 docker 容器之间双向复制文件docker cp并且还可以使用 docker py 从运行的容器中获取文件 但我无法弄清楚如何 或者甚至可能 使用 docker py 将文件从主机复制到正在运行的容器
  • UIPopoverController 更改 UIPopoverArrowDirection 的位置

    我想移动UIPopoverArrowDirection向左移动 使得实际箭头不再居中 而是从左侧多出 25 从右侧多出 75 为了帮助理解我的意思 请参阅两个屏幕截图 我希望它像第二个一样 问题是没有真正的方法来深入研究 popover 类
  • 使用 Python 和 dotenv 更改保存在 .env 文件中的环境变量

    我正在尝试使用 python 更新 env 环境变量 和os environ我可以查看和更改本地环境变量 但我想更改 env 文件 使用python dotenv我可以将 env 条目加载到本地环境变量中 env 文件 key value
  • 如何在 concourse-docker 中使用本地 docker 镜像作为资源

    我尝试在 docker 映像中运行任务 该映像没有上传到 docker hub 而是安装在本地 使用docker build t tagname tagname 所以它存在于 唯一的 工人身上 image resource type doc
  • 将 Windows 身份验证与 OAuth 2.0 结合使用

    我已经设置了一个 OWIN 授权服务器和几个公开 ASP NET Web API 的资源服务器 我正在从授权服务器提供一个特定于每个资源服务器的 JWT 其想法是每个资源服务器都需要将自定义声明封装在其令牌中 这些服务器都位于 Intran
  • 暂时将[u8]转变为[u16]

    我有一个 u8 16384 and a u16 我如何 暂时改变 数组以便我可以设置两个u8一次 第一个到最低有效字节 第二个到最高有效字节 最明显 安全且可移植的方法就是只使用数学 fn set u16 le a mut u8 v u16
  • 动态 xml 到 mongoDB

    我目前正在开发一个应用程序 Java 并且可以找到解决我的问题的最佳方法 我需要将数据存储在 mongoDB 中 当 bson 格式支持时 使用实际数据类型 我在 xml 文件中获取数据 以及他的模式 两者都是在运行时动态创建的 所以我不知
  • oracle diff:如何比较两个表?

    假设我有两个表 t1 和 t2 它们的布局相同 但可能包含不同的数据 区分这两个表的最佳方法是什么 尝试这个 select from T1 minus select from T2 all rows that are in T1 but n
  • 类型“Store”中缺少属性“[Symbol.observable]”,但类型“Store”中需要属性“[Symbol.observable]”。 TS2741

    更新 react material core 后出现此错误 我不知道如何解决此问题 我尝试更新react redux和redux thunk库但没有成功 这是我的商店文件的一部分 const store isDevMode createSt
  • 通过css替换img的内容

    我有这个图像标签 img src http placehold it 200x200 我需要通过css替换图像 因为我无法编辑html 所以我使用这个css img content url http lorempixel com 200 2
  • 在处理流时如何删除或忽略错误?

    我有一个很长的期货清单 我想使用它们来运行Stream buffer unordered Stream buffered 我将这个流结合成一个未来for each然后用 Tokio 执行这一切 其中一个期货返回错误是很常见的 根据文档 fo
  • 使用引用类型和可为空值类型的 C# 泛型类

    我有一个有趣的问题 我想创建一个通用类 可以处理引用类型以及Nullable
  • 将非平稳转换为平稳

    我有一个数据 它是不是静止的 我正在努力做到固定式 我尝试了对数变换 BoxCox 变换 滞后 1 2 和 3 差异 不使用这些转换和差分 I used adf test测试 R 中的平稳性 谁能告诉我还有其他方法可以使其静止吗 data
  • Rails 应用程序中的缓慢操作:ActionDispatch::Routing::RouteSet#call

    根据 NewRelic 我的应用程序中最慢的事务是 中间件 机架 ActionDispatch Routing RouteSet call 应用程序服务器大约需要 261 毫秒的时间 这是什么RouteSet call Rails 路由机制
  • 配对括号的正则表达式

    输入线是这样的 只是其中的一部分 Text Text Text text text text asdasdasdasda asdasdasdasd 我想要的是列出所有匹配项 其中文本包含在一对中 and 我确实尝试了几种模式 但是当未关闭时
  • 为什么将数据写入文件时出现此错误

    我有这个代码 myvector lt c 3 45235 1 32525 2 41351 some numbers write myvector C mypath myfile txt I use instead of 我收到以下错误 文件
  • javascript window.open 在 safari 中

    我在 safari ipad 和桌面版本 中打开一个新窗口时遇到了一个与弹出窗口阻止程序有关的问题 基本上我发现如果 window open 没有从点击事件中调用 safari 将阻止弹出窗口 调用 window open 的事件当前正在从
  • 在列表视图中添加复选框 (C#)

    我想在 C 应用程序中显示一个列表视图 其中每一行代表一个产品 因此属性 视图 设置为 详细信息 一列 最后一列 应该是一个复选框 因为它代表产品是否打折 将 checkboxes 属性设置为 true 后 第一列中会出现一个复选框 因此它
  • 如何使用php获取oracle 11g xe中最后插入记录的序列id?

    在这里 我试图插入一条记录并检索最后插入的序列ID 但没有取得任何成功 有人可以帮助我 指导我oracle如何与php一起工作吗 query INSERT INTO hist news id headline reportedon repo
  • .NET Core WebAPI 后备 API 版本,以防缺少次要版本

    经过多次尝试和阅读文章后 我决定将我的问题放在这里 我想要的是 我正在研究应用程序的 api 版本控制 NET Core 支持的版本格式 Microsoft AspNetCore Mvc Versioningpackage 是 Major