1.多版本管理概念
什么是API的多版本問題?Android等App存在着多版本客戶端共存的問題:由於早期沒有內置升級機制,用戶不會升級,拒絕升級等原因,造成了許多軟件的舊版本App也在運行。開發新版本App時,要給接口增加新的功能或者修改以前接口的規范,會造成舊版本App無法使用,因此再一定情況下會“保留舊接口的運行,新功能用新接口”,這樣就會存在多版本接口共存的問題。
2.解決方式
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執行
下面以第三個種記錄一個例子
3.解決例題
創建一個WebApi項目,在Controllers中創建各個版本的目錄
然后我們在每個版本下創建一個Home控制器
public class HomeController : ApiController { [HttpGet] public String GetIndex() { return "這是v1版本的Index"; } }
public class HomeController : ApiController { [HttpGet] public String GetIndex() { return "這是v2版本的Index"; } }
正常情況下,我們是不可以在Controllers中創建目錄的,這不符合約定,所以我們必須改寫其中代碼,讓其根據我們需求來選擇控制器。
下面我們創建一個我們自己的IHttpControllerSelector的實現類來替換默認的IHttpControllerSelector。
/// <summary> /// 自己實現IHttpControllerSelector來替換默認IHttpConllerSelector /// </summary> public class VersionConstrollerSelector : IHttpControllerSelector { private readonly HttpConfiguration _conf; public VersionConstrollerSelector(HttpConfiguration configuration) { _conf = configuration; } public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { throw new NotImplementedException(); } public HttpControllerDescriptor SelectController(HttpRequestMessage request) { throw new NotImplementedException(); } }
IHttpControllerSelector接口有兩個方法,
GetControllerMapping():獲取程序中所有的Api接口
SelectController(HttpRequestMessage request):匹配請求的路由
下面我們來重寫這兩個方法
/// <summary> /// 獲取所有Controller /// </summary> /// <returns></returns> public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { Dictionary<String, HttpControllerDescriptor> dict = new Dictionary<string, HttpControllerDescriptor>(); foreach (var item in _conf.Services.GetAssembliesResolver().GetAssemblies()) {//循環所有程序集 //獲取所有繼承自ApiController的非抽象類 var controllerTypes = item.GetTypes() .Where(y => !y.IsAbstract && typeof(ApiController) .IsAssignableFrom(y)).ToArray(); foreach (var ctrlType in controllerTypes) {//循環程序集中類型 //從namespace中提取出版本號 var match = Regex.Match(ctrlType.Namespace,GetType().Namespace+ @".Controllers.v(\d+)"); if(match.Success) {//匹配成功 //獲取版本號 string verNum = match.Groups[1].Value; //從控制器總名稱中拿到控制器名稱(例: HomeController中獲取Home) string ctrlName = Regex.Match(ctrlType.Name, "(.+)Controller").Groups[1].Value; //聲明集合中的鍵 String key = (ctrlName + "v" + verNum).ToLower(); //存儲集合值(控制器信息) dict[key] = new HttpControllerDescriptor(_conf, ctrlName, ctrlType); } } } return dict; } /// <summary> /// 進行匹配Controller /// </summary> /// <param name="request">http請求信息</param> /// <returns>匹配成功返回控制器信息,匹配失敗返回null</returns> public HttpControllerDescriptor SelectController(HttpRequestMessage request) { //獲取所有的Controller集合 var controllers = GetControllerMapping(); //獲取路由數據 var routeData = request.GetRouteData(); //從路由中獲取當前controller的名稱 var controllerName = routeData.Values["Controller"] as String; //如果請求頭中存在ApiVerson信息則總其中獲取版本號否則從url中獲取版本號 var verNum = request.Headers.TryGetValues("ApiVerson", out var versions) ? versions.Single() : Regex.Match(request.RequestUri.PathAndQuery, @"api/v(\d+)").Groups[1].Value; //獲取版本號 var key = (controllerName + "v" + verNum).ToLower();//獲取Personv2 //返回控制器信息 return controllers.ContainsKey(key) ? controllers[key] : null; }
現在我們這個類實現完成以后我們便可以在WebApiConfig類中的Register方法中替換原來的IHttpControllerSelector
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服務 //替換HttpControllerSelector config.Services.Replace(typeof(IHttpControllerSelector), new VersionConstrollerSelector(config)); } }
並且在其方法創建新的路由
public static void Register(HttpConfiguration config) {// Web API 路由 config.MapHttpAttributeRoutes(); 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.Routes.MapHttpRoute( name: "DefaultApiv3", routeTemplate: "api/v3/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); }
至此,我們便成功的以替換IHttpControllerSelector方式來完成了多版本管理