第十一节: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/1和http://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接口來替換系統默認的IHttpControllerSelector3 /// </summary>4 public class VersionConstrollerSelector : DefaultHttpControllerSelector5 {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 WebApiConfig2 {3 public static void Register(HttpConfiguration config)4 {5 // Web API 配置和服務6 //用重寫IHttpControllerSelector的替換原先的IHttpControllerSelector7 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,返回不同版本的信息,證明可以報文頭中的參數進行版本區分。
?
?
?
?
?
總結
以上是生活随笔為你收集整理的第十一节:WebApi的版本管理的几种方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 银行存款逆袭了?最高利率超过5%,傲视大
- 下一篇: 第二十节: 深入理解并发机制以及解决方案