什么是 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/1和http://api.rsfy.com/api/v2/User/1转到不同的服务器处理。
3、 多个版本的 Controller 共处在一个项目中,然后使用 [RoutePrefix] 或者
IHttpControllerSelector 根据报文头、路径等选择不同的 Controller 执行。下面主要讲这方法
(推荐):自定义 IHttpControllerSelector
修改默认路由,如果还想用{controller}/{action}的方式,那么改就是了
其中v1,v2代表这版本号,不同版本的 Controller 放到不同的 namespace 下
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了。