1. 程式人生 > >WebAPI 的多版本管理

WebAPI 的多版本管理

什麼是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在請求同名的控制器的時候,就會自動呼叫對應版本的名稱空間下的同名控制器