• api.versioning 版本控制 自动识别最高版本


    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();
        }
    }
    View Code

    其中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

  • 相关阅读:
    TSQL Beginners Challenge 1
    SQL拾遗
    简易实体生成方式
    CTE-递归[2]
    编号处理
    行列转换/横转竖
    OUTPUT、Merge语句的使用
    关于SQL IO的一些资料
    对左值(lvalue)和右值(rvalue)的两种理解方式
    跨平台判断64位和32位开发的一些宏定义
  • 原文地址:https://www.cnblogs.com/wangpengzong/p/13040338.html
Copyright © 2020-2023  润新知