打造屬於自己的支持版本迭代的Asp.Net Web Api Route

来源:http://www.cnblogs.com/inday/archive/2017/07/13/custom-version-webapi-route.html
-Advertisement-
Play Games

在目前的主流架構中,我們越來越多的看到web Api的存在,小巧,靈活,基於Http協議,使它在越來越多的微服務項目或者移動項目充當很好的service endpoint。問題 以Asp.Net Web Api 為例,隨著業務的擴展,產品的迭代,我們的web api也在隨之變化,很多時候會出現多個版... ...


    在目前的主流架構中,我們越來越多的看到web Api的存在,小巧,靈活,基於Http協議,使它在越來越多的微服務項目或者移動項目充當很好的service endpoint。

問題

    以Asp.Net Web Api 為例,隨著業務的擴展,產品的迭代,我們的web api也在隨之變化,很多時候會出現多個版本共存的現象,這個時候我們就需要設計一個支持版本號的web api link,比如:

原先:http://www.test.com/api/{controller}/{id}

如今:http://www.test.com/api/{version}/{controller}/{id}

在我們剛設計的時候,有可能沒有考慮版本的問題,我看到很多的項目都會在link後加入一個“?version=”的方式,這種方式確實能夠解決問題,但對Asp.Net Web Api來說,進入的還是同一個Controller,我們需要在同一個Action中進行判斷版本號,例如:

http://www.test.com/api/bolgs?version=v2[HttpGet]

public class BlogsController : ApiController
{
    // GET api/<controller>
    public IEnumerable<string> Get([FromUri]string version = "")
    {
        if (!String.IsNullOrEmpty(version))
        {
            return new string[] { $"{version} blog1", $"{version} blog2" };
        }
        return new string[] { "blog1", "blog2" };
    }
}

我們看到我們通過判斷url中的version參數進行對應的返回,為了確保原先介面的可用,我們需要對參數賦上預設值,雖然能夠解決我們的版本迭代問題,但隨著版本的不斷更新,你會發現這個Controller會越來越臃腫,維護越來越困難,因為這種修改已經嚴重違反了OCP(Open-Closed Principle),最好的方式是不修改原先的Controller,而是新建新的Controller,放在對應的目錄中(或者項目中),比如:

image

為了不影響原先的項目,我們儘量不要改動原Controller的Namespace,除非你有十足的把握沒有影響,不然請儘量只是移動到目錄。

ok,為了保持原介面的映射,我們需要在WebApiConfig.Register中註冊支持版本號的Route映射:

config.Routes.MapHttpRoute(
    name: "DefaultVersionApi",
    routeTemplate: "api/{version}/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);

打開瀏覽器或者postman,輸入原先的api url,你會發現這樣的錯誤:

image

那是因為web api 查找Controller的時候,只會根據ClassName進行查找的,當出現相同ClassName的時候,就會報這個錯誤,這時候我們就需要打造自己的Controller Selector,好在微軟留了一個介面給到我們:IHttpControllerSelector。不過為了相容原先的api(有些不在我們許可權範圍內的api,不加版本號的那種),我們還是直接集成DefaultHttpControllerSelector比較好,我們給定一個規則,不負責我們版本迭代的api,就讓它走原先的映射。

思路

1、項目啟動的時候,先把符合條件的Controller加入到一個字典中

2、判斷request,符合規則的,我們返回我們制定的controller。

image

image

打造屬於自己的Selector

思路有了,那改造起來也非常簡單,今天我們先做一個簡單的,等有時間改成可配置的。

第一步,我們先創建一個Selector類,繼承自DefaultHttpControllerSelector,然後初始化的時候創建一個屬於我們自己的字典:

public class VersionHttpControllerSelector : DefaultHttpControllerSelector
{
    private readonly HttpConfiguration _configuration;
    private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _lazyMappingDictionary;
    private const string DefaultVersion = "v1"; //預設版本號,因為之前的api我們沒有版本號的概念
    private const string DefaultNamespaces = "WebApiVersions.Controllers"; //為了演示方便,這裡就用到一個命名空間
    private const string RouteVersionKey = "version"; //路由規則中Version的字元串
    private const string DictKeyFormat = "{0}.{1}";
    public VersionHttpControllerSelector(HttpConfiguration configuration):base(configuration)
    {
        _configuration = configuration;
        _lazyMappingDictionary = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDict);
    }

    private Dictionary<string, HttpControllerDescriptor> InitializeControllerDict()
    {
        var result = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
        var assemblies = _configuration.Services.GetAssembliesResolver();
        var controllerResolver = _configuration.Services.GetHttpControllerTypeResolver();
        var controllerTypes = controllerResolver.GetControllerTypes(assemblies);

        foreach(var t in controllerTypes)
        {
            if (t.Namespace.Contains(DefaultNamespaces)) //符合NameSpace規則
            {
                var segments = t.Namespace.Split(Type.Delimiter);
                var version = t.Namespace.Equals(DefaultNamespaces, StringComparison.OrdinalIgnoreCase) ?
                    DefaultVersion : segments[segments.Length - 1];
                var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
                var key = string.Format(DictKeyFormat, version, controllerName);
                if (!result.ContainsKey(key))
                {
                    result.Add(key, new HttpControllerDescriptor(_configuration, t.Name, t));
                }
            }
        }

        return result;
    }
}


有了字典接下來就好辦了,只需要分析request就好了,符合我們版本要求的,就從我們的字典中查找對應的Descriptor,如果找不到,就走預設的,這裡我們需要重寫SelectController方法:

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
    IHttpRouteData routeData = request.GetRouteData();
    if (routeData == null)
        throw new HttpResponseException(HttpStatusCode.NotFound);

    var controllerName = GetControllerName(request);
    if (String.IsNullOrEmpty(controllerName))
        throw new HttpResponseException(HttpStatusCode.NotFound);

    var version = DefaultVersion;
    if (IsVersionRoute(routeData, out version))
    {
        var key = String.Format(DictKeyFormat, version, controllerName);
        if (_lazyMappingDictionary.Value.ContainsKey(key))
        {
            return _lazyMappingDictionary.Value[key];
        }

        throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    return base.SelectController(request);
}

private bool IsVersionRoute(IHttpRouteData routeData, out string version)
{
    version = String.Empty;
    var prevRouteTemplate = "api/{controller}/{id}";
    object outVersion;
    if(routeData.Values.TryGetValue(RouteVersionKey, out outVersion))   //先找符合新規則的路由版本
    {
        version = outVersion.ToString();
        return true;
    }

    if (routeData.Route.RouteTemplate.Contains(prevRouteTemplate))  //不符合再比對是否符合原先的api路由
    {
        version = DefaultVersion;
        return true;
    }

    return false;
}

完成這個類後,我們去WebApiConfig.Register中進行替換操作:

config.Services.Replace(typeof(IHttpControllerSelector), new VersionHttpControllerSelector(config));

ok,再次打開瀏覽器,輸入http://www.xxx.com/api/blogs 和 http://www.xxx.com/api/v2/blogs ,這時應該能看到正確的執行:

image

image

寫在最後

今天我們打造了一個簡單符合webapi版本號更新迭代的ControllerSelector,不過還不是很完善,因為很多都是hard code,後面我會做一個支持配置的ControllerSelector放到github上。

之前一直在研究eShopOnContrainers,最近也在研究,不過工作確實有點忙,見諒見諒,如果大家.Net有什麼問題或者喜歡技術交友的,都可以加QQ群:376248054


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 恢復內容開始 最近在研究.NET分散式緩存代碼,正好涉及Lock,看了網上的文章,總結了一些Lock相關的知識,供大家一起學習參考。 一、Lock定義 lock 關鍵字可以用來確保代碼塊完成運行,而不會被其他線程中斷。它可以把一段代碼定義為互斥段(critical section),互斥段在一個時刻 ...
  • 問題: 在WIN7中的IIS伺服器中部署WCF服務程式時,通過瀏覽器訪問報出如下錯誤: 未能從程式集“System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089” 中載入類型 “Syst ...
  • 在views文件下找到web.config 註釋掉選中這句話 ...
  • 詳情就是點擊詳情後彈出一個div,所以需要現在boby裡面先建立一個div 這個div是需要隱藏的,當點擊詳情再彈出來。(隱藏語句需要放在頁面載入的函數中) 在上一篇的datagrid裡面我給詳情的超鏈接添加了一個 onclick="showDetail('+row.Id+')" 事件 row.Id ...
  • 1、在Model裡面建立NewInfo(裡面存放的是新聞信息的實體信息) 然後在DAL層中建立NewInfoDal (裡面存放對新聞信息的操作) 寫入分頁查詢的代碼 在BLL層中建立NewInfoServices(裡面存放對新聞信息的邏輯處理) 我們把新聞管理的url指定為/NewInfo/Inde ...
  • 今天給大家帶來一個Visual Studio 2013中非常實用的功能,自動生成XML反序列化的類。以往想要在代碼中將XML反序列化成對象,我們要麼手動創建這些對象(很容易出錯),要麼藉助於第三方的工具來生成Class。而現在,Visual Studio 2013可以自動幫我們完成這個工作。當然,如 ...
  • 一:TCP粘包產生的原理 1,TCP粘包是指發送方發送的若幹包數據到接收方接收時粘成一包,從接收緩衝區看,後一包數據的頭緊接著前一包數據的尾。出現粘包現象的原因是多方面的,它既可能由發送方造成,也可能由接收方造成。 2,發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集 ...
  • 引言 之前學習了一點關於緩存的東西,有控制器緩存、頁面緩存,又看到一篇文章是關於部分視圖緩存的內容。一下就是我的一些學習總結。 情景 假設有一個頁面A,這是一個靜態頁面除了頭條的輪播圖需要更新。那麼這個時候可以把整個頁面緩存,然後輪播圖那一塊用Html.Partial顯示。 首先頁面緩存設置為一個小 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...