在目前的主流架构中,我们越来越多的看到web Api的存在,小巧,灵活,基于Http协议,使它在越来越多的微服务项目或者移动项目充当很好的service endpoint。

问题

以Asp.Net Web Api 为例,随着业务的扩展,产品的迭代,我们的web api也在随之变化,很多时候会出现多个版本共存的现象,这个时候我们就需要设计一个支持版本号的web api link,比如:

原先:http://www.test.com/api/{controller}/{id}

如今:http://www.test.com/api/{version}/{controller}/{id}

在我们刚设计的时候,有可能没有考虑版本的问题,我看到很多的项目都会在link后加入一个“?version=”的方式,这种方式确实能够解决问题,但对Asp.Net Web Api来说,进入的还是同一个Controller,我们需要在同一个Action中进行判断版本号,例如:

http://www.test.com/api/bolgs?version=v2[HttpGet]

public class BlogsController : ApiController
{
// GET api/<controller>
public IEnumerable<string> Get([FromUri]string version = "")
{
if (!String.IsNullOrEmpty(version))
{
return new string[] { $"{version} blog1", $"{version} blog2" };
}
return new string[] { "blog1", "blog2" };
}
}

我们看到我们通过判断url中的version参数进行对应的返回,为了确保原先接口的可用,我们需要对参数赋上默认值,虽然能够解决我们的版本迭代问题,但随着版本的不断更新,你会发现这个Controller会越来越臃肿,维护越来越困难,因为这种修改已经严重违反了OCP(Open-Closed Principle),最好的方式是不修改原先的Controller,而是新建新的Controller,放在对应的目录中(或者项目中),比如:

为了不影响原先的项目,我们尽量不要改动原Controller的Namespace,除非你有十足的把握没有影响,不然请尽量只是移动到目录。

ok,为了保持原接口的映射,我们需要在WebApiConfig.Register中注册支持版本号的Route映射:

config.Routes.MapHttpRoute(
name: "DefaultVersionApi",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);

打开浏览器或者postman,输入原先的api url,你会发现这样的错误:

那是因为web api 查找Controller的时候,只会根据ClassName进行查找的,当出现相同ClassName的时候,就会报这个错误,这时候我们就需要打造自己的Controller Selector,好在微软留了一个接口给到我们:IHttpControllerSelector。不过为了兼容原先的api(有些不在我们权限范围内的api,不加版本号的那种),我们还是直接集成DefaultHttpControllerSelector比较好,我们给定一个规则,不负责我们版本迭代的api,就让它走原先的映射。

思路

1、项目启动的时候,先把符合条件的Controller加入到一个字典中

2、判断request,符合规则的,我们返回我们制定的controller。


打造属于自己的Selector

思路有了,那改造起来也非常简单,今天我们先做一个简单的,等有时间改成可配置的。

第一步,我们先创建一个Selector类,继承自DefaultHttpControllerSelector,然后初始化的时候创建一个属于我们自己的字典:

public class VersionHttpControllerSelector : DefaultHttpControllerSelector
{
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _lazyMappingDictionary;
private const string DefaultVersion = "v1"; //默认版本号,因为之前的api我们没有版本号的概念
private const string DefaultNamespaces = "WebApiVersions.Controllers"; //为了演示方便,这里就用到一个命名空间
private const string RouteVersionKey = "version"; //路由规则中Version的字符串
private const string DictKeyFormat = "{0}.{1}";
public VersionHttpControllerSelector(HttpConfiguration configuration):base(configuration)
{
_configuration = configuration;
_lazyMappingDictionary = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDict);
} private Dictionary<string, HttpControllerDescriptor> InitializeControllerDict()
{
var result = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
var assemblies = _configuration.Services.GetAssembliesResolver();
var controllerResolver = _configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllerResolver.GetControllerTypes(assemblies); foreach(var t in controllerTypes)
{
if (t.Namespace.Contains(DefaultNamespaces)) //符合NameSpace规则
{
var segments = t.Namespace.Split(Type.Delimiter);
var version = t.Namespace.Equals(DefaultNamespaces, StringComparison.OrdinalIgnoreCase) ?
DefaultVersion : segments[segments.Length - 1];
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = string.Format(DictKeyFormat, version, controllerName);
if (!result.ContainsKey(key))
{
result.Add(key, new HttpControllerDescriptor(_configuration, t.Name, t));
}
}
} return result;
}
}

有了字典接下来就好办了,只需要分析request就好了,符合我们版本要求的,就从我们的字典中查找对应的Descriptor,如果找不到,就走默认的,这里我们需要重写SelectController方法:

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
throw new HttpResponseException(HttpStatusCode.NotFound); var controllerName = GetControllerName(request);
if (String.IsNullOrEmpty(controllerName))
throw new HttpResponseException(HttpStatusCode.NotFound); var version = DefaultVersion;
if (IsVersionRoute(routeData, out version))
{
var key = String.Format(DictKeyFormat, version, controllerName);
if (_lazyMappingDictionary.Value.ContainsKey(key))
{
return _lazyMappingDictionary.Value[key];
} throw new HttpResponseException(HttpStatusCode.NotFound);
} return base.SelectController(request);
} private bool IsVersionRoute(IHttpRouteData routeData, out string version)
{
version = String.Empty;
var prevRouteTemplate = "api/{controller}/{id}";
object outVersion;
if(routeData.Values.TryGetValue(RouteVersionKey, out outVersion)) //先找符合新规则的路由版本
{
version = outVersion.ToString();
return true;
} if (routeData.Route.RouteTemplate.Contains(prevRouteTemplate)) //不符合再比对是否符合原先的api路由
{
version = DefaultVersion;
return true;
} return false;
}

完成这个类后,我们去WebApiConfig.Register中进行替换操作:

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

ok,再次打开浏览器,输入http://www.xxx.com/api/blogs 和 http://www.xxx.com/api/v2/blogs ,这时应该能看到正确的执行:

写在最后

今天我们打造了一个简单符合webapi版本号更新迭代的ControllerSelector,不过还不是很完善,因为很多都是hard code,后面我会做一个支持配置的ControllerSelector放到github上。

之前一直在研究eShopOnContrainers,最近也在研究,不过工作确实有点忙,见谅见谅,如果大家.Net有什么问题或者喜欢技术交友的,都可以加QQ群:376248054

打造属于自己的支持版本迭代的Asp.Net Web Api Route的更多相关文章

  1. 通过微软的cors类库,让ASP.NET Web API 支持 CORS

    前言:因为公司项目需要搭建一个Web API 的后端,用来传输一些数据以及文件,之前有听过Web API的相关说明,但是真正实现的时候,感觉还是需要挺多知识的,正好今天有空,整理一下这周关于解决COR ...

  2. ASP.NET Web API 2 对 CORS 的支持

    CORS概念 跨域资源共享 (CORS) 是一种万维网联合会 (W3C) 规范(通常被认为是 HTML5 的一部分),它可让 JavaScript 克服由浏览器施加的同域策略安全限制. 所谓同域策略, ...

  3. 尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性

    本文首发于<尝新体验ASP.NET Core 6预览版本中发布的最小Web API(minimal APIS)新特性> 概述 .NET开发者们大家好,我是Rector. 几天前(美国时间2 ...

  4. 通过扩展让ASP.NET Web API支持JSONP

    同源策略(Same Origin Policy)的存在导致了"源"自A的脚本只能操作"同源"页面的DOM,"跨源"操作来源于B的页面将会被拒 ...

  5. ASP.NET Web API 2.1支持Binary JSON(Bson)

    ASP.NET Web API 2.1内建支持XML.Json.Bson.form-urlencoded的MiME type,今天重点介绍下Bson.BSON是由10gen开发的一个数据格式,目前主要 ...

  6. 通过扩展让ASP.NET Web API支持W3C的CORS规范

    让ASP.NET Web API支持JSONP和W3C的CORS规范是解决"跨域资源共享"的两种途径,在<通过扩展让ASP.NET Web API支持JSONP>中我们 ...

  7. ASP.NET Web API自身对CORS的支持:从实例开始

    在<通过扩展让ASP.NET Web API支持W3C的CORS规范>中我们通过自定义的HttpMessageHandler为ASP.NET Web API赋予了跨域资源共享的能力,具体来 ...

  8. ASP.NET Web API自身对CORS的支持: EnableCorsAttribute特性背后的故事

    从编程的角度来讲,ASP.NET Web API针对CORS的实现仅仅涉及到HttpConfiguration的扩展方法EnableCors和EnableCorsAttribute特性.但是整个COR ...

  9. ASP.NET Web API自身对CORS的支持: CORS授权检验的实施

    通过<EnableCorsAttribute特性背后的故事>我们知道:由CorsPolicyProvider提供的CorsPolicy表示目标Action采用的资源授权策略,ASP.NET ...

随机推荐

  1. OpenStack命令 创建网络和路由管理

    1.登陆用户 :tdy(前提条件创建了tdy用户) 编写登陆用户tdy用户 脚本文件  user-operc.sh user-operc.sh : 登陆用户tdy: $ source user-ope ...

  2. es6之Generator

    1.Generator函数其实是一个封装了多个内部状态的状态机,执行它会返回一个遍历器对象,然后可以依次遍历Generator中的每一个状态,也就是分段执行,yield是暂停执行的标记,next恢复执 ...

  3. Kotlin入门第三课:数据类型

    前文链接: Kotlin学习第一课:从对比Java开始 Kotlin入门第二课:集合操作 初次尝试用Kotlin实现Android项目 Kotlin的数据类型与Java类似,因此这篇文章主要看Kotl ...

  4. 【知识整理】惊现RecyclerView内部Bug???别急,我们慢慢解决它~

    一.写在前面 这也是久违的一整个月没有写Blog,也是由于近期给妹纸找工作,各种坑蒙拐骗,然而都没卵用.额,广大朋友们,成都需要软件测试.线上运维.产品助理的伙伴,赶紧私聊我了.这妹纸,学习能力挺好, ...

  5. AngularJS语法基础及数据绑定——详解各种数据绑定指令、属性应用

    AngularJS简单易学,但是功能强大.特别是在构建单页面应用方面效果显著.而 数据绑定 可以说是他被广泛使用的最主要的优点.他舍弃了对DOM的操作方式,一切都由AngularJS来自动更新视图,我 ...

  6. 快学scala

    scala 1.   scala的由来 scala是一门多范式的编程语言,一种类似java的编程语言[2] ,设计初衷是要集成面向对象编程和函数式编程的各种特性. java和c++的进化速度已经大不如 ...

  7. js的for循环闭包问题

    一个简单的例子,如果想循环输出数组中的每一个数值我们可以利用for循环来输出例如: <script type="text/javascript"> var arr=[& ...

  8. 你是否也在学习ES6 Promise时遇到过这个问题?

    背景 周末闲来无事,随便翻看了一下阮一峰老师的<ES6 标准入门>第2版,ps:之前在阮一峰老师的官网看过电子版,感觉干货满满,所以就买了纸质版:当看到第16章第4节 'Promise.p ...

  9. ue4打包问题的巧妙解决——二分回退大法!

    昨天突然发生了一件非常恐怖的事--我的ue4项目居然不能打包了!! 大概是这么一回事:  UATHelper: 打包 (Windows (64位)): UnrealBuildTool: ERROR:  ...

  10. 玩转nodeJS系列:使用cluster创建nodejs单机多核集群(多进程)

    前言: nodejs提供了cluster集群(支持端口共享的多进程),cluster基于child_process,process二次封装,方便我们使用该功能实现单机nodejs的web集群. 1.c ...