WebApi的多版本管理

时间:2020-12-03 08:53:48

什么是 API 的多版本问题?Android 等 App 存在着多版本客户端共存的问题:App 最新版已经升级到了 5.0 了,但是有的用户手机上还运行着 4.8、3.9 甚至 2.2 版本的 App,由于早期没有内置升级机制、用户不会升级、用户拒绝升级等原因,造成这些旧版本 App 也在运行。开发新版本 App 的时候,要给接口增加新的功能或者修改以前接口的规范,会造成旧版本 App 无法使用,因此在一定情况下会“保留旧接口的运行、新功能用新接口”,这样就会存在多版本接口共存的问题。

 通常的做法是:旧版接口做一个代码分支,除了进行 bug 修改外,旧版本接口不再做改动;新接口代码继续演化升级。在客户端请求的时候带着要请求的接口版本号,在服务器端选择合适的版本代码进行处理。

技术处理方法:

1、  (最推荐)不同版本用不同的域名:v1.api.rsfy.com、v2.api.rsfy.com、v3……;

2、  在 url、报文头等中带不同的版本信息,用 Nginx 等做反向代理服务器,然后将 http://api.rsfy.com/api/v1/User/1http://api.rsfy.com/api/v2/User/1到不同的服务器处理。

3、  多个版本的 Controller 共处在一个项目中,然后使用 [RoutePrefix] 或者

IHttpControllerSelector 根据报文头、路径等选择不同的 Controller 执行。下面主要讲这方法

 (推荐):自定义 IHttpControllerSelector

修改默认路由,如果还想用{controller}/{action}的方式,那么改就是了

其中v1,v2代表这版本号,不同版本的 Controller 放到不同的 namespace 下

WebApi的多版本管理

 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 }
            );

首先自定义类VersionControllerSelector继承DefaultHttpControllerSelector

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Dispatcher;

namespace ApiDemo
{
    public class VersionControllerSelector:DefaultHttpControllerSelector
    {
        private readonly HttpConfiguration _config;

        public VersionControllerSelector(HttpConfiguration config) : base(config)
        {
            _config = config;
        }

        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中提取出版本号                  命名空间,有可能不是当前的weiapi项目
                    var match = Regex.Match(ctrlType.Namespace, GetType().Namespace + @".Controllers.v(\d+)");
                    if (match.Success)
                    {
                        string verNum = match.Groups[1].Value;//获取版本号  
                        //从PersonController中拿到Person
                        string ctrlName = Regex.Match(ctrlType.Name, "(.+)Controller").Groups[1].Value;
                        //Personv2为key                
                        string key = ctrlName + "v" + verNum;

                        dict[key] = new HttpControllerDescriptor(_config, ctrlName, ctrlType);
                    }
                }
            }
            return dict;
        }

        //设计就是返回HttpControllerDesriptor的过程 
        public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            //获取所有的controller键值集合 
            var controllers = GetControllerMapping();
            //获取路由数据 
            var routeData = request.GetRouteData();
            //从路由中获取当前controller的名称 
            var controllerName = (string)routeData.Values["controller"];

            var verNum = request.Headers.TryGetValues("ApiVersion", out var versions) ?
                versions.Single() :
                Regex.Match(request.RequestUri.PathAndQuery, @"api/v(\d+)").Groups[1].Value;


            //获取版本号 

            var key = controllerName + "v" + verNum;//获取Personv2             
            return controllers.ContainsKey(key) ? controllers[key] : null;
        }
    }
}

然后在WebApiConfig 的 Register 中添加

 config.Services.Replace(typeof(IHttpControllerSelector), new VersionControllerSelector(config));

最后我们就可以以不同的版本号,来访问不同的controller了。