WebAPI 的多版本管理
阿新 • • 發佈:2019-01-23
什麼是API 的多版本問題?
Android 等App 存在著多版本客戶端共存的問題:App 最新版已經升級到了5.0了,但是有的使用者手機上還執行著4.8、3.9甚至2.2版本的App,由於早期沒有內建升級機制、使用者不會升級、使用者拒絕升級等原因,造成這些舊版本App也在執行。
開發新版本App的時候,要給介面增加新的功能或者修改以前介面的規範,會造成舊版本App無法使用,因此在一定情況下會“保留舊介面的執行、新功能用新介面”,這樣就會存在多版本介面共存的問題。
通常的做法是:舊版介面做一個程式碼分支,除了進行bug修改外,舊版本介面不再做改動;新介面程式碼繼續演化升級。在客戶端請求的時候帶著要請求的介面版本號,在伺服器端選擇合適的版本程式碼進行處理。技術處理方法:
1> (最推薦)不同版本用不同的域名:v1.api.rupeng.com、v2.api.rupeng.com、v3……;
2> 在url、報文頭等中帶不同的版本資訊,用Nginx等做反向代理伺服器,然後將http://api.rupeng.com/api/v1/User/1和http://api.rupeng.com/api/v2/User/1轉到不同的伺服器處理。
3> 多個版本的Controller共處在一個專案中,然後使用[RoutePrefix]或者IHttpControllerSelector
現在我們來看看多個版本的Controller共處在一個專案中這種情況下我們的如何處理
處理方法1:
通過個控制器和方法打RoutePrefix標籤和Route標籤的方式,根據路由規則,來選擇哪個控制器
例如:
http://localhost:14483/api/v1/Person/1 這個URL地址的請求由PersonController控制來處理
http://localhost:14483/api/v2/Person/1 這個URL地址的請求由PersonV2Controller控制來處理
namespace WebApi.Controllers { [RoutePrefix("api/v1/Person")] public class PersonController : ApiController { [Route("{id}")] public string Get(int id) { return "我是舊版" + id; } } [RoutePrefix("api/v2/Person")] public class PersonV2Controller : ApiController { [Route("{id}")] public string Get(int id) { return "我是V2版" + id; } } }
處理方法2(推薦):自定義IHttpControllerSelector
第一步:根據版本號配置路由
namespace WebApi { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服務 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); //根據版本號來註冊路由:第v1版本的請求地址:http://localhost:14483/api/v1/Person/Get config.Routes.MapHttpRoute( name: "DefaultApiv1", routeTemplate: "api/v1/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); //根據版本號來註冊路由:第v2版本的請求地址:http://localhost:14483/api/v2/Person/Get config.Routes.MapHttpRoute( name: "DefaultApiv2", routeTemplate: "api/v2/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); //將自定義的VersionControllerSelector註冊到系統中 config.Services.Replace(typeof(IHttpControllerSelector),new VersionControllerSelector(config)); } } }
第二步:建立一個VersionControllerSelector.cs類
namespace WebApi.App_Start
{
public class VersionControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
IDictionary<string, HttpControllerDescriptor> controllers = null;//快取用
public VersionControllerSelector(HttpConfiguration config) : base(config)
{
_config = config;
}
//設計就是返回HttpControllerDesriptor的過程
public override System.Web.Http.Controllers.HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
//獲取所有的controller鍵值集合
if (controllers == null)
{
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;//獲取Personv2
if (controllers.ContainsKey(key))//獲取HttpControllerDescriptor
{
return controllers[key];
}
else
{
return null;
}
}
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中提取出版本號
var match = Regex.Match(ctrlType.Namespace,
@"WebApi.Controllers.v(\d+)");
if (match.Success)
{
string verNum = match.Groups[1].Value;//獲取版本號
string ctrlName = Regex.Match(ctrlType.Name, "(.+)Controller").Groups[1].Value;//從PersonController中拿到Person
string key = ctrlName + "v" + verNum;//Personv2為key
dict[key] = new HttpControllerDescriptor(_config, ctrlName, ctrlType);
}
}
}
controllers = dict;//因為專案啟動的時候就會呼叫GetControllerMapping這個方法,這個方法主要是就獲取所有的控制器,所以既然專案開始啟動的時候就已經呼叫過這個方法了,已經獲取到了所有的控制器了,為了避免我們在重新SelectController方法的時候二次呼叫,這裡把已經取到的控制器字典快取起來。
return dict;
}
}
}
第三步:在WebApiConfig.cs中 將以下程式碼加入到Register方法最後面
//將自定義的VersionControllerSelector註冊到系統中
config.Services.Replace(typeof(IHttpControllerSelector),new VersionControllerSelector(config));
第四步: 在WebApi專案中的Controllers檔案下分別建立v1和v2兩個資料夾。 並在兩個檔案中分別建立一個PersonController.cs控制器
例如:v1資料夾下的PersonController.cs控制器
namespace WebApi.Controllers.v1
{
public class PersonController : ApiController
{
public string Get()
{
return "我是v1";
}
}
}
v2資料夾下的PersonController.cs控制器
namespace WebApi.Controllers.v2
{
public class PersonController : ApiController
{
public string Get()
{
return "我是v2";
}
}
}
第五步:請求呼叫
v1版本的App發起請求的URL是這樣的:http://localhost:14483/api/v1/Person/Get
v2版本的App發起請求的URL是這樣的:http://localhost:14483/api/v2/Person/Get
這樣就達到了不同版本的APP在請求同名的控制器的時候,就會自動呼叫對應版本的名稱空間下的同名控制器