主頁 > .NET開發 > api.versioning 版本控制 自動識別最高版本

api.versioning 版本控制 自動識別最高版本

2020-09-12 21:05:04 .NET開發

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

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/17082.html

標籤:.NET Core

上一篇:[經驗堆疊]C#監測IPv4v6網速及流量

下一篇:結合 AOP 輕松處理事件發布處理日志

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more