什么是 API 的多版本問題?Android 等 App 存在着多版本客戶端共存的問題:App 最新版已經升級到了 5.0 了,但是有的用戶手機上還運行着 4.8、3.9 甚至 2.2 版本的 App,由於早期沒有內置升級機制、用戶不會升級、用戶拒絕升級等原因,造成這些舊版本 App 也在運行。開發新版本 App 的時候,要給接口增加新的功能或者修改以前接口的規范,會造成舊版本 App 無法使用,因此在一定情況下會“保留舊接口的運行、新功能用新接口”,這樣就會存在多版本接口共存的問題。
通常的做法是:舊版接口做一個代碼分支,除了進行 bug 修改外,舊版本接口不再做改動;新接口代碼繼續演化升級。在客戶端請求的時候帶着要請求的接口版本號,在服務器端選擇合適的版本代碼進行處理。
技術處理方法:
1、 (最推薦)不同版本用不同的域名:v1.api.rsfy.com、v2.api.rsfy.com、v3……;
2、 在 url、報文頭等中帶不同的版本信息,用 Nginx 等做反向代理服務器,然后將 http://api.rsfy.com/api/v1/User/1和http://api.rsfy.com/api/v2/User/1轉到不同的服務器處理。
3、 多個版本的 Controller 共處在一個項目中,然后使用 [RoutePrefix] 或者
IHttpControllerSelector 根據報文頭、路徑等選擇不同的 Controller 執行。下面主要講這方法
(推薦):自定義 IHttpControllerSelector
修改默認路由,如果還想用{controller}/{action}的方式,那么改就是了
其中v1,v2代表這版本號,不同版本的 Controller 放到不同的 namespace 下
config.Routes.MapHttpRoute( name: "DefaultApiv1", routeTemplate: "api/v1/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional }); config.Routes.MapHttpRoute( name: "DefaultApiv2", routeTemplate: "api/v2/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } );
首先自定義類VersionControllerSelector繼承DefaultHttpControllerSelector
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using System.Web; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Dispatcher; namespace ApiDemo { public class VersionControllerSelector:DefaultHttpControllerSelector { private readonly HttpConfiguration _config; public VersionControllerSelector(HttpConfiguration config) : base(config) { _config = config; } public override IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { Dictionary<string, HttpControllerDescriptor> dict = new Dictionary<string, HttpControllerDescriptor>(); foreach (var asm in _config.Services.GetAssembliesResolver().GetAssemblies()) { //獲取所有繼承自ApiController的非抽象類 var controllerTypes = asm.GetTypes() .Where(t => !t.IsAbstract && typeof(ApiController) .IsAssignableFrom(t)).ToArray(); foreach (var ctrlType in controllerTypes) { //從namespace中提取出版本號 命名空間,有可能不是當前的weiapi項目 var match = Regex.Match(ctrlType.Namespace, GetType().Namespace + @".Controllers.v(\d+)"); if (match.Success) { string verNum = match.Groups[1].Value;//獲取版本號 //從PersonController中拿到Person string ctrlName = Regex.Match(ctrlType.Name, "(.+)Controller").Groups[1].Value; //Personv2為key string key = ctrlName + "v" + verNum; dict[key] = new HttpControllerDescriptor(_config, ctrlName, ctrlType); } } } return dict; } //設計就是返回HttpControllerDesriptor的過程 public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { //獲取所有的controller鍵值集合 var controllers = GetControllerMapping(); //獲取路由數據 var routeData = request.GetRouteData(); //從路由中獲取當前controller的名稱 var controllerName = (string)routeData.Values["controller"]; var verNum = request.Headers.TryGetValues("ApiVersion", out var versions) ? versions.Single() : Regex.Match(request.RequestUri.PathAndQuery, @"api/v(\d+)").Groups[1].Value; //獲取版本號 var key = controllerName + "v" + verNum;//獲取Personv2 return controllers.ContainsKey(key) ? controllers[key] : null; } } }
然后在WebApiConfig 的 Register 中添加
config.Services.Replace(typeof(IHttpControllerSelector), new VersionControllerSelector(config));
最后我們就可以以不同的版本號,來訪問不同的controller了。