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

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。