什么是API 的多版本问题?
Android 等App 存在着多版本客户端共存的问题:App 最新版已经升级到了5.0了,但是有的用户手机上还运行着4.8、3.9甚至2.2版本的App,由于早期没有内置升级机制、用户不会升级、用户拒绝升级等原因,造成这些旧版本App也在运行。
开发新版本App的时候,要给接口增加新的功能或者修改以前接口的规范,会造成旧版本App无法使用,因此在一定情况下会“保留旧接口的运行、新功能用新接口”,这样就会存在多版本接口共存的问题。
技术处理方法:
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在请求同名的控制器的时候,就会自动调用对应版本的名称空间下的同名控制器