天天看點

WebApi的多版本管理

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

WebApi的多版本管理
   然後我們在每個版本下建立一個Home控制器
WebApi的多版本管理

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

WebApi的多版本管理
WebApi的多版本管理

繼續閱讀