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中创建各个版本的目录
然后我们在每个版本下创建一个Home控制器
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方式来完成了多版本管理