第十一節:WebApi的版本管理的幾種方式


一. 背景和方案

1. 多版本管理的概念

  Android 、IOS等 App 存在着多版本客戶端共存的問題:App 最新版已經升級到了5.0 了,但是有的用戶手機上還運行着 4.8、3.9 甚至2.2 版本的 App,由於早期沒有內置升級機制、用戶不會升級、用戶拒絕升級等原因,造成這些舊版本 App 也在運行。開發新版本 App 的時候,要給接口增加新的功能或者修改以前接口的規范,會造成舊版本App 無法使用,因此在一定情況下會“保留舊接口的運行、新功能用新接口”,這樣就會存在多版本接口共存的問題。

  通常的做法是:舊版接口做一個代碼分支,除了進行 bug 修改外,舊版本接口不再做改動,新接口代碼繼續演化升級。在客戶端請求的時候帶着要請求的接口版本號,在服務器端選擇合適的版本代碼進行處理。

2. 解決方案

(1). 不同的版本使用不同的域名:v1.api.ypf.com、v2.api.ypf.com、v3……  (最佳方案)

(2). 在Url,報文頭等中帶不同的版本信息,用Nginx等做反向代理服務,然后將 http://api.ypf.com/api/v1/User/1http://api.ypf.com/api/v2/User/1 轉到不同的服務器處理。

(3). 多個版本的 Controller共處在一個項目中,然 后使 用 [RoutePrefix] 特性來進行區分,這種方案Controller的名字不能一樣,如下:

 

(4). 如果我想在Controller文件夾中新建多個版本文件夾,如:v1、v2、v3,每個文件夾中存放的控制器名稱相同,比如都叫PersonController,不同文件夾下代表不同版本,這個時候會有一個很尷尬的問題,沒法請求,識別不了,這個時候就需要重寫系統默認的機制,IHttpControllerSelector 根據 “報文頭”或者“請求路徑”等選擇不同的 Controller 執行。

該方案的實現,詳見下面的實戰測試

 

 

二. 實戰測試

 1. 在Controller文件下新建v1和v2文件夾,分別存放不同版本的Person控制器,每個Person控制器新建一個GetName方法,如下圖:

 

 2. 重寫系統默認的控制器選擇機制,可以直接實現IHttpControllerSelector接口,也可以繼承DefaultHttpControllerSelector類,從而間接實現IHttpControllerSelector接口,在重寫的SelectController方法中,實現了兩種區分機制,分別是根據請求路徑區分 和 根據報文頭中的ApiVersion參數區分。

 1     /// <summary>
 2     /// 自己實現IHttpControllerSelector接口來替換系統默認的IHttpControllerSelector
 3     /// </summary>
 4     public class VersionConstrollerSelector : DefaultHttpControllerSelector
 5     {
 6 
 7         private HttpConfiguration _config;
 8         /// <summary>
 9         /// 構造函數
10         /// </summary>
11         /// <param name="config"></param>
12         public VersionConstrollerSelector(HttpConfiguration config) : base(config)
13         {
14             _config = config;
15         }
16 
17 
18         /// <summary>
19         /// 獲取所有的Controller
20         /// </summary>
21         /// <returns></returns>
22         public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
23         {
24             Dictionary<string, HttpControllerDescriptor> dict = new Dictionary<string, HttpControllerDescriptor>();
25             //循環所有的程序集
26             foreach (var asm in _config.Services.GetAssembliesResolver().GetAssemblies())
27             {
28                 //獲取所有繼承ApiController的非抽象類
29                 var controllerTypes = asm.GetTypes().Where(t => !t.IsAbstract && typeof(ApiController).IsAssignableFrom(t)).ToArray();
30                 //循環上述獲取的非抽象類
31                 foreach (var ctrlType in controllerTypes)
32                 {
33                     //從namespace中提取版本號
34                     var match = Regex.Match(ctrlType.Namespace, @"_05_WebApiExtend.Controllers.v(\d+)");
35                     if (match.Success)
36                     {
37                         //獲取版本號
38                         string verNum = match.Groups[1].Value;
39                         //獲取控制器名稱(eg:PersonController中獲取Person)
40                         string controllerName = Regex.Match(ctrlType.Name, "(.+)Controller").Groups[1].Value;
41                         //聲明集合中的鍵(形式:Personv1 、Personv2)
42                         string key = controllerName + "v" + verNum;
43                         dict[key] = new HttpControllerDescriptor(_config, controllerName, ctrlType);
44                     }
45 
46                 }
47 
48             }
49             return dict;
50         }
51 
52         /// <summary>
53         /// 進行Controller的匹配
54         /// </summary>
55         /// <param name="request"></param>
56         /// <returns>匹配成功返回控制器信息,匹配失敗返回null</returns>
57 
58         public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
59         {
60             //獲取所有的controller鍵值集合
61             var controllers = GetControllerMapping();
62             //獲取路由數據
63             var routeData = request.GetRouteData();
64             //從路由中獲取當前controller的名稱
65             var controllerName = (string)routeData.Values["controller"];
66 
67             //下面是兩種方式獲取版本號
68             string verNum = "";
69             try
70             {
71                 //從報文頭中獲取版本號(當沒有這個參數的時候走catch)
72                 verNum = request.Headers.GetValues("ApiVersion").Single();
73             }
74             catch (Exception)
75             {
76                 //從url中獲取版本號
77                 verNum = Regex.Match(request.RequestUri.PathAndQuery, @"api/v(\d+)").Groups[1].Value;
78             }
79             //拼接key值
80             string key = controllerName + "v" + verNum;
81             if (controllers.ContainsKey(key))
82             {
83                 return controllers[key];
84             }
85             else
86             {
87                 return null;
88             }
89         }
90     }

3.  在WebConfig.cs類中,加上 config.Services.Replace(typeof(IHttpControllerSelector), new VersionConstrollerSelector(config)); ,即用重寫IHttpControllerSelector的替換原先的IHttpControllerSelector,如下圖:

 

4.  注釋掉原先的路由請求模式,新增下面兩個路由規則

 1  public static class WebApiConfig
 2     {
 3         public static void Register(HttpConfiguration config)
 4         {
 5             // Web API 配置和服務
 6             //用重寫IHttpControllerSelector的替換原先的IHttpControllerSelector
 7             config.Services.Replace(typeof(IHttpControllerSelector), new VersionConstrollerSelector(config));
 8             // Web API 路由
 9             config.MapHttpAttributeRoutes();
10             //config.Routes.MapHttpRoute(
11             //    name: "DefaultApi",
12             //    routeTemplate: "api/{controller}/{action}/{id}",
13             //    defaults: new { id = RouteParameter.Optional }
14             //);
15 
16             //多版本控制的路由改造
17             config.Routes.MapHttpRoute(
18               name: "DefaultApiV1",
19               routeTemplate: "api/v1/{controller}/{action}/{id}",
20               defaults: new { id = RouteParameter.Optional }
21              );
22 
23             config.Routes.MapHttpRoute(
24              name: "DefaultApiV2",
25              routeTemplate: "api/v2/{controller}/{action}/{id}",
26              defaults: new { id = RouteParameter.Optional }
27             );
28         }
29     }

5. 用PostMan測試

(1). 分別請求 http://localhost:2182/api/v1/Person/GetName?Name=2   和 http://localhost:2182/api/v2/Person/GetName?Name=2, 返回不同的版本的信息,證明可以根據Url中的v1和v2進行版本區分。

(2). 請求 http://localhost:2182/api/v1/Person/GetName?Name=2 地址兩次 ,表頭中分別帶有ApiVersion=1 和 ApiVersion=2,返回不同版本的信息,證明可以報文頭中的參數進行版本區分。

 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 本人才疏學淺,用郭德綱的話說“我是一個小學生”,如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM