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中建立各個版本的目錄

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方式來完成了多版本管理