1.什么是 API 的多版本?
說白了就是多版本共存的問題。為方便大家理解我就舉個例子吧,大家想必都用過Jquery吧,它的1.*版本做到了對低版本IE的支持;2.*版本還保留着ajax,但是不再支持老舊瀏覽器;3.*版本連ajax都不留了;但是用戶不會升級、用戶拒絕升級等原因,造成這些舊版本也需要運行,但是新版卻已經修改了規范與舊版舊版沖突了。造成這些舊版本也需要運行使用。再例如我們手機有Android4.0、5.0、6.0、7.0、8.0或IOS8.0、9.0、10、11同時存在於市場也是類似的。
2.出現多版本問題我們通常的做法
舊版接口做成一個分支,除了進行 bug 修改外,舊版本接口不再做改動;新接口代碼繼續演化升級。在客戶端請求的時候帶着要請求的接口版本號,在服務器端選擇合適的版本代碼進行處理。
3.技術處理方案
(1)(最推薦)不同版本用不同的域名:v1.api.jiyuwu.com、v2.api.jiyuwu.com、v3……。
(2) 在url、報文頭等中帶不同的版本信息,用 Nginx 等做反向代理服務器,然后將 http://api.jiyuwu.com/api/V1/Login/1和http://api.jiyuwu.com/api/V2/Login/1轉到不同的服務器處理。
(3) 多 個 版 本 的 Controller 共 處 在 一 個 項 目 中 , 然 后 使 用 [RoutePrefix] 或 者 IHttpControllerSelector 根據報文頭、路徑等選擇不同的 Controller 執行。下面主要講這兩種方法。
4.針對3.(3)的兩種方案的案例
(1)[RoutePrefix] 案例
舊版保持原樣不改變
public class LoginController : ApiController { [HttpGet] public string ToLogin(int id) { return "這是舊版" + id; } }
新版代碼同時用路由處理
[RoutePrefix("api/V2/Login")] public class LoginV2Controller : ApiController { [Route("{id}")] [HttpGet] public string ToLogin(int id) { return "這是新版" + id; } }

(2) IHttpControllerSelector 案例
項目結構如圖
(1)添加VersionnControllerSelector類
public class VersionnControllerSelector : DefaultHttpControllerSelector { public HttpConfiguration _config; public VersionnControllerSelector(HttpConfiguration config) : base(config) { _config = config; } public override IDictionary<string, System.Web.Http.Controllers.HttpControllerDescriptor> GetControllerMapping() { Dictionary<string, HttpControllerDescriptor> dic = new Dictionary<string, HttpControllerDescriptor>(); foreach (var ams in _config.Services.GetAssembliesResolver().GetAssemblies()) { //獲取繼承自ApiControl的非抽象類 var controlTypes = ams.GetTypes().Where(p => !p.IsAbstract && typeof(ApiController).IsAssignableFrom(p)).ToArray(); foreach (var ctrlType in controlTypes) { //從namespace中提取出版本號 var match = Regex.Match(ctrlType.Namespace, @"MoreVersionContorl.Controllers.V(\d+)"); if (match.Success) { string verNum = match.Groups[1].Value;//獲取版本號 string ctrlName = Regex.Match(ctrlType.Name, "(.+)Controller").Groups[1].Value;//從LoginController中拿到Login string key = ctrlName + "V" + verNum;//Personv2為key dic[key] = new HttpControllerDescriptor(_config, ctrlName, ctrlType); } } } return dic; } public override System.Web.Http.Controllers.HttpControllerDescriptor SelectController(HttpRequestMessage request) { //獲取所有Controller集合 var controllers = GetControllerMapping(); //獲取路由數據 var routeData = request.GetRouteData(); //從路由中獲取當前controller的名稱 var controllerName = (string)routeData.Values["controller"]; //從url中獲取到版本號 string verNum = Regex.Match(request.RequestUri.PathAndQuery, @"api/V(\d+)").Groups[1].Value; string key = controllerName + "V" + verNum;//獲取Loginv2 if (controllers.ContainsKey(key))//獲取HttpControllerDescriptor { return controllers[key]; } else { return null; } } }
(2)創建V1和V2下的兩個控制器
1) V1下的LoginController
public class LoginController : ApiController { public string Get(int id) { return "This Version is V1,id=" + id; } }
2) V2下的LoginController
public class LoginController : ApiController { public string Get(int id) { return "This Version is V2,id="+id; } }
(3)修改WebApiConfig
public static void Register(HttpConfiguration config) { // Web API 配置和服務 // Web API 路由 config.MapHttpAttributeRoutes(); //config.Routes.MapHttpRoute( // name: "DefaultApi", // routeTemplate: "api/{controller}/{id}", // defaults: new { id = RouteParameter.Optional } //); 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 } ); config.Services.Replace(typeof(IHttpControllerSelector), new VersionnControllerSelector(config)); }
(4)配置完畢請求結果如圖:

