api.versioning 版本控制 自动识别最高版本和多Area但同名Contoller问题解决办法
Microsoft.AspNetCore.Mvc.Versioning //引入程序集
.net core 下面api的版本控制作用不需要多说,可以查阅https://www.cnblogs.com/dc20181010/p/11313738.html
普通的版本控制一般是通过链接、header此类方法进行控制,对ApiVersionReader进行设置,例如
services.AddApiVersioning(o => {
//o.ReportApiVersions = true;//返回版本可使用的版本
o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader("api-version"));//通过Header或QueryString进行传值来判断api的版本
//o.DefaultApiVersion = new ApiVersion(1, 0);//默认版本号
});
或者使用https://www.cnblogs.com/tdfblog/p/asp-net-core-api-versioning.html这种方式
这两种方式都需要传递api的版本信息,如果不传递将会报错
{"error":{"code":"ApiVersionUnspecified","message":"An API version is required, but was not specified.","innerError":null}}
如果我们不想传递api的版本信息时,可以将
o.AssumeDefaultVersionWhenUnspecified = true; //此选项将用于在没有版本的情况下提供请求
o.DefaultApiVersion = new ApiVersion(1, 0); //设置默认Api版本是1.0
打开,这个我们每次请求如果不传递版本信息也不会报错了,但我们的请求将会指向1.0版本,那么我想让默认版本指向我写的api里面的最高版本怎么做?
我们将默认版本修改为最高版本可以吗?
这里将会出现一个问题,我的api版本可能由于各种各样原因造成最高版本不一致的问题
所以我们不能采用指定默认版本是最高版本的方法来解决,这个最高版本还必须要是动态的,通过翻阅https://github.com/microsoft/aspnet-api-versioning/wiki/API-Version-Selector#current-implementation-api-selector可以得知
The CurrentImplementationApiVersionSelector selects the maximum API version available which does not have a version status.
If no match is found, it falls back to the configured DefaultApiVersion. For example, if the versions "1.0", "2.0", and "3.0-Alpha" are available,
then "2.0" will be selected because it's the highest, implemented or released API version. CurrentImplementationApiVersionSelector选择不具有版本状态的最大可用API版本。 如果找不到匹配项,它将回退到配置的DefaultApiVersion。
例如,如果提供版本“ 1.0”,“ 2.0”和“ 3.0-Alpha”,则将选择“ 2.0”,因为它是最高,已实施或已发布的API版本。
services.AddApiVersioning(
options => options.ApiVersionSelector =
new CurrentImplementationApiVersionSelector( options ) );
通过这个版本选择器,我们可以将最大版本得出,修改上面services.AddApiVersioning
services.AddApiVersioning(o => {
o.ReportApiVersions = true;//返回版本可使用的版本
//o.ApiVersionReader = new UrlSegmentApiVersionReader();
//o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader("api-version"));
//o.ApiVersionReader = ApiVersionReader.Combine(new QueryStringApiVersionReader("api-version"));
o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"));//版本号以什么形式,什么字段传递
o.AssumeDefaultVersionWhenUnspecified = true;//此选项将用于在没有版本的情况下提供请求
o.DefaultApiVersion = new ApiVersion(1, 0);//默认版本号
o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);//默认以当前最高版本进行访问
});
举个栗子
namespace Default.v1.Controllers
{
[ApiVersion("1.0")]
[Route("[controller]/[action]")]
[ApiController]
public class HomeController : Controller, IBaseController
{
private readonly ILogger<HomeController> _logger; public HomeController (ILogger<HomeController> logger)
{
_logger = logger;
} public JsonResult GetJson()
{
return Json("Home 1.0");
}
}
Default.v1.Controllers.Home
namespace Default.v2.Controllers
{
[ApiVersion("2.0")]
[Route("[controller]/[action]")]
[ApiController]
public class HomeController : Controller, IBaseController
{
private readonly ILogger<HomeController> _logger; public HomeController (ILogger<HomeController> logger)
{
_logger = logger;
} public JsonResult GetJson()
{
return Json("Home 2.0");
}
}
Default.v2.Controllers.Home
namespace Default.v1.Controllers
{
[ApiVersion("1.0")]
[Route("[controller]/[action]")]
[ApiController]
public class TestController : Controller, IBaseController
{
private readonly ILogger<HomeController> _logger; public TestController (ILogger<HomeController> logger)
{
_logger = logger;
} public JsonResult GetJson()
{
return Json("Test 1.0");
}
}
Default.v1.Controllers.Test
我们在
请求/home/getjson 时返回“Home 2.0”
请求/test/getjson 时返回“Test 1.0”
这样就可以动态的请求最高版本了
但是还是会有问题的,比如,在我添加了Area和User区域下的HomeController,且User区域下的HomeController增加了1.0和3.0版本之后,神奇的一幕出现了
我的HomeController进不去了。。。
{"error":{"code":"UnsupportedApiVersion","message":"The HTTP resource that matches the request URI 'https://localhost:44311/home/getjson' is not supported.","innerError":null}}
这个时候去google都查不到原因。。。
查看api-supported-versions,返回的是1.0,2.0,3.0。。。我的api版本控制被污染了3.0版本从哪里来的哪?第一反应是从User区域来的
我现在在User区域下添加一个除了Home和Test以外Name的Controller就可以请求成功,这个让我怀疑到是不是api.versioning本身的问题,首先怀疑的是Controller的Name问题,源码拉取下来,从添加版本控制的地方(services.AddApiVersioning)开始找

最后终于在ApiVersionCollator中找到了蛛丝马迹
///https://github.com/microsoft/aspnet-api-versioning/blob/master/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersionCollator.cs namespace Microsoft.AspNetCore.Mvc.Versioning
{
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq; /// <summary>
/// Represents an object that collates <see cref="ApiVersion">API versions</see> per <see cref="ActionDescriptor">action</see>.
/// </summary>
[CLSCompliant( false )]
public class ApiVersionCollator : IActionDescriptorProvider
{
readonly IOptions<ApiVersioningOptions> options; /// <summary>
/// Initializes a new instance of the <see cref="ApiVersionCollator"/> class.
/// </summary>
/// <param name="options">The current <see cref="ApiVersioningOptions">API versioning options</see>.</param>
public ApiVersionCollator( IOptions<ApiVersioningOptions> options ) => this.options = options; /// <summary>
/// Gets the API versioning options associated with the collator.
/// </summary>
/// <value>The current <see cref="ApiVersioningOptions">API versioning options</see>.</value>
protected ApiVersioningOptions Options => options.Value; /// <inheritdoc />
public int Order { get; protected set; } /// <inheritdoc />
public virtual void OnProvidersExecuted( ActionDescriptorProviderContext context )
{
if ( context == null )
{
throw new ArgumentNullException( nameof( context ) );
} foreach ( var actions in GroupActionsByController( context.Results ) )
{
var collatedModel = CollateModel( actions ); foreach ( var action in actions )
{
var model = action.GetProperty<ApiVersionModel>(); if ( model != null && !model.IsApiVersionNeutral )
{
action.SetProperty( model.Aggregate( collatedModel ) );
}
}
}
} /// <inheritdoc />
public virtual void OnProvidersExecuting( ActionDescriptorProviderContext context ) { } /// <summary>
/// Resolves and returns the logical controller name for the specified action.
/// </summary>
/// <param name="action">The <see cref="ActionDescriptor">action</see> to get the controller name from.</param>
/// <returns>The logical name of the associated controller.</returns>
/// <remarks>
/// <para>
/// The logical controller name is used to collate actions together and aggregate API versions. The
/// default implementation uses the "controller" route parameter and falls back to the
/// <see cref="ControllerActionDescriptor.ControllerName"/> property when available.
/// </para>
/// <para>
/// The default implementation will also trim trailing numbers in the controller name by convention. For example,
/// the type "Values2Controller" will have the controller name "Values2", which will be trimmed to just "Values".
/// This behavior can be changed by using the <see cref="ControllerNameAttribute"/> or overriding the default
/// implementation.
/// </para>
/// </remarks>
protected virtual string GetControllerName( ActionDescriptor action )
{
if ( action == null )
{
throw new ArgumentNullException( nameof( action ) );
} if ( !action.RouteValues.TryGetValue( "controller", out var key ) )
{
if ( action is ControllerActionDescriptor controllerAction )
{
key = controllerAction.ControllerName;
}
} return TrimTrailingNumbers( key );
} IEnumerable<IEnumerable<ActionDescriptor>> GroupActionsByController( IEnumerable<ActionDescriptor> actions )
{
var groups = new Dictionary<string, List<ActionDescriptor>>( StringComparer.OrdinalIgnoreCase ); foreach ( var action in actions )
{
var key = GetControllerName( action ); if ( string.IsNullOrEmpty( key ) )
{
continue;
} if ( !groups.TryGetValue( key, out var values ) )
{
groups.Add( key, values = new List<ActionDescriptor>() );
} values.Add( action );
} foreach ( var value in groups.Values )
{
yield return value;
}
} static string TrimTrailingNumbers( string? name )
{
if ( string.IsNullOrEmpty( name ) )
{
return string.Empty;
} var last = name!.Length - 1; for ( var i = last; i >= 0; i-- )
{
if ( !char.IsNumber( name[i] ) )
{
if ( i < last )
{
return name.Substring( 0, i + 1 );
} return name;
}
} return name;
} static ApiVersionModel CollateModel( IEnumerable<ActionDescriptor> actions ) => actions.Select( a => a.GetApiVersionModel() ).Aggregate();
}
}
其中GroupActionsByController将Controller按照Controller的名字进行分组,再看看内部,分组的时候将GetControllerName( action )作为key,那么GetControllerName是干嘛的,
protected virtual string GetControllerName( ActionDescriptor action )
{
if ( action == null )
{
throw new ArgumentNullException( nameof( action ) );
} if ( !action.RouteValues.TryGetValue( "controller", out var key ) )
{
if ( action is ControllerActionDescriptor controllerAction )
{
key = controllerAction.ControllerName;
}
} return TrimTrailingNumbers( key );
}
这个方法原本是没有问题的,但是牵扯到Area的时候就会出问题了。。它将根目录下的HomeController和User.HomeController视为同一类的Controller然后去做版本的属性注入,造成CurrentImplementationApiVersionSelector选择器选不到正确的版本,所以返回了上面的错误,我们将GetControllerName内部修改为
protected virtual string GetControllerName( ActionDescriptor action )
{
if ( action == null )
{
throw new ArgumentNullException( nameof( action ) );
} if ( !action.RouteValues.TryGetValue( "controller", out var key ) )
{
if ( action is ControllerActionDescriptor controllerAction )
{
key = controllerAction.ControllerName;
}
} if ( !action.RouteValues.TryGetValue( "area", out var area ) )
{
} return TrimTrailingNumbers( area + key );
}
这样就可以走通了
我们有两种解决办法,一个是把源码拉取下来,方法修改掉,项目的依赖项替换为自己修改的Microsoft.AspNetCore.Mvc.Versioning,另一种办法是将services.AddApiVersioning重写。。。请相信我,拉取修改替换依赖比重写services.AddApiVersioning快且简便。。。
issue:https://github.com/microsoft/aspnet-api-versioning/issues/630
api.versioning 版本控制 自动识别最高版本和多Area但同名Contoller问题解决办法的更多相关文章
- api.versioning 版本控制 自动识别最高版本
Microsoft.AspNetCore.Mvc.Versioning //引入程序集 .net core 下面api的版本控制作用不需要多说,可以查阅https://www.cnblogs.com/ ...
- API服务版本控制 Microsoft.AspNetCore.Mvc.Versioning
我们在进行webapi服务开发时,会遇到一些多个版本的api共存的情况发生,例如某一版本APP上线后,需求发生变更,需要在下一个升级版本更新API,但同时又需要保证这个APP版本能正常使用,这时候就需 ...
- Web API 接口版本控制 SDammann.WebApi.Versioning
前言 在设计对外 Web API 时,实务上可能会有新旧版本 API 并存的情况,例如开放 Web API 给厂商串接,但同一个服务更新版本时,不一定所有厂商可以在同一时间都跟着更新他们的系统,但如果 ...
- 从壹开始前后端分离 [.netCore 填坑 ] 三十四║Swagger:API多版本控制,带来的思考
前言 大家周二好呀,.net core + Vue 这一系列基本就到这里差不多了,今天我又把整个系列的文章下边的全部评论看了一下(我是不是很负责哈哈),提到的问题基本都解决了,还有一些问题,已经在QQ ...
- 对API进行版本控制的重要性和实现方式
我在API设计中收到的最常见问题之一就是如何对API进行版本控制.虽然并非所有API都完全相同,但我发现在API版本控制方面,某些模式和实践适用于大多数团队.我已经将这些内容收集起来,下面将提供一些关 ...
- Semantic Versioning Specification & 语义化版本
Semantic Versioning Specification & 语义化版本 Semantic Versioning Specification http://semver.org 16 ...
- android 6 (API 23) 及更高版本 面向 NDK 开发者的 Android 变更
Android N 已经出来,有了好大的变化,对于我们开发者来说,最大的影响莫过于**NDK**相关东西. 以下是在中国谷歌开发者社区看到的.里面有好多的变化,欢迎大家来讨论. 发布人:开发顾问 Dm ...
- 分享vs低版本开发的项目到VS高版本时遇到的4个小问题解决之记录
分享vs低版本开发的项目到VS高版本时遇到的4个小问题解决之记录 原文首发: http://anforen.com/wp/2017/08/extensionattribute_compilerserv ...
- pycharm(社区版2019.1版本)打开README.md文件卡死解决办法
现象:pycharm(社区版2019.1版本)打开README.md文件卡死 解决办法: 将插件Markdown support前的勾选√去掉,保存修改后重启pycharm即可
随机推荐
- html单页面通过cdn引入element-ui组件样式不显示问题
html单页面通过cdn引入element-ui组件样式不显示问题 必须先引入vue,再通过cdn引入element,否则element-ui组件与样式无效. <!DOCTYPE html> ...
- 2┃音视频直播系统之浏览器中通过 WebRTC 拍照片加滤镜并保存
一.拍照原理 好多人小时候应该都学过,在几张空白的纸上画同一个物体,并让物体之间稍有一些变化,然后连续快速地翻动这几张纸,它就形成了一个小动画,音视频播放器就是利用这样的原理来播放音视频文件的 播放器 ...
- 拯救一切强迫症 - 读《编写可维护的 JavaScript》(一)
拯救一切强迫症 - 读<编写可维护的 JavaScript>(一) 本文写于 2020 年 4 月 24 日 我在小学的时候就有接触过编程,所以读大一的时候 C 语言还算是轻车熟路.自然会 ...
- linux篇-CentOS7搭建NTP服务器
1查看服务器.客户端操作系统版本 2查看服务器是否安装ntp 3如果没有安装 4安装完成后重新查看服务器是否安装ntp 5查看ntp服务器状态 6修改配置文件 注释 #server 0.centos. ...
- Hadoop安装学习(第四天)
学习任务:解决9000端口丢失导致hadoop无法连接的问题 解决方法:格式化namenode 步骤: 1.进入hadoop/bin 2.输入命令:hadoop namenode -format(hd ...
- 浅谈BSGS和EXBSGS
我的 BSGS 和各位犇犇的差不多,但是不需要求逆元 Luogu [ TJOI2007 ] 可爱的质数 原题展现 题目描述 给定一个质数 \(p\),以及一个整数 \(b\),一个整数 \(n\),现 ...
- 在linux上开启酸酸乳,未完待续
在服务器调试深度学习环境的时候总需要下载conda的包,一直以来都觉得是因为国内访问慢,于是想在服务器上开 ,或者ssr.由于过去用ssr多一些,于是想了解ssr on linux. 1.首先win1 ...
- GDB的简单使用一
GDB的简单使用一 一.概念 二.GDB的基本使用方法一 调试前预备知识 获取进程的内核转储 启动gdb调试 1.启动 2.设置断点 3.运行程序 4.显示栈帧 5.显示变量 6.显示寄存器 7.单步 ...
- JS:String
String数据类型:字符串 字符串是存储字符的变量. 字符串可以是引号中(可以使用单引号或双引号)的任意文本. var a = "abc"; var b = "123& ...
- Mybatis架构原理(二)-二级缓存源码剖析
Mybatis架构原理(二)-二级缓存源码剖析 二级缓存构建在一级缓存之上,在收到查询请求时,Mybatis首先会查询二级缓存,若二级缓存没有命中,再去查询一级缓存,一级缓存没有,在查询数据库; 二级 ...