Asp.Net Web Api Route

 

在目前的主流架构中,我们越来越多的看到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

Net Web Api Route的更多相关文章

  1. web api Route属性定义

    ASP.NET Web API路由,简单来说,就是把客户端请求映射到对应的Action上的过程.在"ASP.NET Web API实践系列03,路由模版, 路由惯例, 路由设置"一 ...

  2. 打造属于自己的支持版本迭代的Asp.Net Web Api Route

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

  3. Web Api Route 注册要放在 Mvc Route 注册前

    今天想研究一下Web Api,写了一个测试Api,打开网站后浏览一下,可是却提示找不到方法,刚开始以为哪里配置错了,可找了半天也没见. 因为我是在一个现有Mvc站点做的Demo,所以打算新建一个Mvc ...

  4. 【ASP.NET MVC 5】第27章 Web API与单页应用程序

    注:<精通ASP.NET MVC 3框架>受到了出版社和广大读者的充分肯定,这让本人深感欣慰.目前该书的第4版不日即将出版,现在又已开始第5版的翻译,这里先贴出该书的最后一章译稿,仅供大家 ...

  5. Web API使用记录系列(一)创建API项目与基本配置

    本系列文章主要记录Web API使用过程中的一些个人总结,包括创建API项目.基础配置.ApiTestClient使用与HelpPage页面的优化.Owin与OAuth的使用等. 本节主要内容是API ...

  6. Web Api通过Route、RoutePrefix等特性设置路由

    [Route("customers/{customerId}/orders")] [HttpGet] public IEnumerable<Order> FindOrd ...

  7. ASP.NET Web API实践系列04,通过Route等特性设置路由

    ASP.NET Web API路由,简单来说,就是把客户端请求映射到对应的Action上的过程.在"ASP.NET Web API实践系列03,路由模版, 路由惯例, 路由设置"一 ...

  8. .net web api 的route理解

    .NET web api 的特性是和MVC一样,通过Route 来控制action的访问方式.Route匹配规则是个奇特的方式,首先看一段Route的模板 Routes.MapHttpRoute( n ...

  9. Web API (四) 特性路由(Attribute Route)

    特性路由 是Web API 2 中提出的一种新的类型的路由,正如其名称那样,它是通过特性(Attribute) 来定义路由的,相比之前的基于模式(Convertion Based)的路由,特性路由 能 ...

随机推荐

  1. 【BZOJ1044】[HAOI2008]木棍分割(动态规划,贪心)

    [BZOJ1044][HAOI2008]木棍分割(动态规划,贪心) 题面 BZOJ 洛谷 题解 第一问随便二分一下就好了,贪心\(check\)正确性显然. 第二问随便前缀和+单调队列优化一下\(dp ...

  2. Libre 6009 「网络流 24 题」软件补丁 / Luogu 2761 软件安装问题 (最短路径,位运算)

    Libre 6009 「网络流 24 题」软件补丁 / Luogu 2761 软件安装问题 (最短路径,位运算) Description T 公司发现其研制的一个软件中有 n 个错误,随即为该软件发放 ...

  3. 图形化调试工具DDD

    ubuntu安装DDD: sudo apt-get install ddd If you still need to compile it for some reason you should be ...

  4. Helm一:简介

    目录 什么是Helm Helm解决的问题 Helm原理 Helm架构 Helm功能 Helm三个重要概念 Helm组件 Helm Client Tiller Server Helm实现 什么是Helm ...

  5. OpenStack 云主机深入了解(十四)

    云主机深入了解 1.云主机在计算节点以进程方式运行 2.监听vnc的端口,vnc默认端口从5900开始, 多台云主机,端口递增 3.云主机桥接网卡,与宿主机联通网络 提示:在openstack环境下, ...

  6. 浅说搜索引擎和SEO

    搜索引擎 基本工作原理 信息收集功能 技术点SEO优化核心 优化重点 SEO优化 网站URL title信息 meta信息 图片ALT flash信息 frame框架 网页重要度特征 之前有看到一个师 ...

  7. OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型

    OpenGL ES 2.0 Shader 调试新思路(二): 做一个可用的原型 目录 背景介绍 请参考前文OpenGL ES 2.0 Shader 调试新思路(一): 改变提问方式 优化 ledCha ...

  8. 蓝桥杯 带分数 DFS应用

    问题描述 100 可以表示为带分数的形式:100 = 3 + 69258 / 714. 还可以表示为:100 = 82 + 3546 / 197. 注意特征:带分数中,数字1~9分别出现且只出现一次( ...

  9. Python学习笔记5-时间模块time/datetime

    import time time.sleep(2) #等待几秒 # 1.格式化好的时间 2018-1-14 16:42 # 2.时间戳 是从unix元年到现在所有的秒数 # 3.时间元组 #想时间戳和 ...

  10. [整理]IIS 6.0 下部署 Asp.net MVC Web Api 后 HTTP PUT and DELETE 请求失败

    http://guodong.me/?p=1560 ASP.NET MVC 4 has a new feature called WebAPI which makes it much easier t ...