ASP.NET Web API编程——路由
路由过程大致分为三个阶段:
1)请求URI匹配已存在路由模板
2)选择控制器
3)选择操作
1匹配已存在的路由模板
路由模板
在WebApiConfig.Register方法中定义路由,例如模板默认生成的路由为:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
上面使用了public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults)方法来配置路由。相关参数为:
name:路由名称。
routeTemplate:路由模板,与URI相似。
例如:
api/{controller}/{id}、
api/{controller}/{action}/{id}、api/{controller}/public/{category}/{id}
defaults:路由值对象。可为占位符设置默认值。
例如
api/{controller}/public/{category}/{id}
设置defaults: new { category = "all" }
路由词典
如果Web API匹配到一个已存在的路由模板,会创建一个路由词典,词典的键是模板中占位符的名称,值是占位符对应的值。如果路由值对象被指定为RouteParameter.Optional,那么这个值不会被放入词典中。路由词典会被存储到IHttpRouteData实例中。
匹配示例
对于api/{controller}/{id}
首先匹配字符串api,然后匹配控制器(controller),第三匹配以HTTP方法开头的操作(Action),占位符id匹配Action接收的参数。
对于api/{controller}/{action}/{id}
首先匹配字符串api,然后匹配控制器(controller),最后匹配操作(Action),占位符id匹配Action接收的参数。
对于api/root/{id}
务必对defaults设置控制器(controller)的默认值,,不然无法执行路由过程。可以不设置操作(Action)。首先匹配api和root,然后匹配默认的控制器(controller),最后占位符id匹配操作(Action)接收的参数。若不设置操作(Action)那么匹配以HTTP方法开头的操作(Action)。
2控制器的选择
控制器(controller)的选择是由IHttpControllerSelector.SelectController完成的,IHttpControllerSelector接口默认实现是DefaultHttpControllerSelector。IHttpControllerSelector.SelectController方法获取HttpRequestMessage实例并返回HttpControllerDescriptor。
DefaultHttpControllerSelector查找控制器(controller)的算法为:
在路由词典中查找键为“controller”的值,找到键“controller”对应的值后,将字符串Controller拼接到这个值的后边,便可获得控制器(Controller)名。根据获得的控制器(Controller)名查找Web API中的控制器(controller)。如果没有查找到控制器(controller)名或者匹配到了多个,那么返回错误。DefaultHttpControllerSelector使用IHttpControllerTypeResolver来获得Web API控制器(controller)类型列表。IHttpControllerTypeResolver的默认实现返回具有如下特征的公有类:
1)实现了IHttpController接口。
2)不被abstract修饰。
3)命名以“Controller”结尾。
3匹配控制器操作
IHttpActionSelector.SelectAction方法获取HttpControllerContext并返回HttpActionDescriptor,IHttpActionSelector接口的默认实现是ApiControllerActionSelector。ApiControllerActionSelector会查找请求的HTTP方法、路由模板中的{action}占位符、控制器操作的参数列表。
Web API框架认为控制器(controller)的操作(Action)具有如下特征:
1)公有类型的实例方法。
2)继承自ApiController的方法
3)非构造器,事件,操作符重载等特殊方法。
Web API框架仅选择那些匹配请求的HTTP方法的操作,原则为:
1)指定了相应特性的操作,例如使用HttpGet特性的操作,只能匹配Get请求。
2)如果控制器(controller)操作以"Get", "Post", "Put", "Delete", "Head", "Options", or "Patch"开头,按照惯例控制器(controller)操作支持对应的HTTP请求。
3)如果不满足以上两条,默认支持POST请求。
ApiControllerActionSelector选择控制器(controller)操作的算法如下:
1)创建一个链表,链表元素为所有与HTTP请求相匹配的操作(Action)。
2)如果路由词典中包含关于操作(Action)的键值对,移除链表中名称和值不匹配的操作(Action)。
3)匹配操作(Action)参数与URI。
l 对于每一个操作(Action),获得简单类型的参数列表,参数绑定从URI获得操作(Action)参数,不包括可选的参数。
l 在参数列表中,从路由表中或请求URI查询字符串中,为每一个参数名找到一个匹配,匹配是不区分大小写的,并且不依赖于参数顺序。
l 选择一个操作(Action),其参数列表中的每一个参数在请求URI中都对应一个值。
l 如果有多个操作(Action)满足以上规则,选择有最多参数匹配的一个操作(Action)。
4)忽略被标记为[NonAction]的方法。
补充说明:
对于步骤3)一个参数可以从URI,请求消息体,或者自定义绑定中获得它的值。对于来自于URI的参数,要确保URI确实包含对应参数的值,这个值可能在路由词典中或查询字符串中。
对于可选的参数,如果绑定不能从URI中获得参数的值,对于操作(Action)的选择也没有影响。
对于复杂类型,只能通过自定义绑定来匹配URI中的参数值。操作(Action)选择算法的目的是在完成模型绑定之前选出操作(Action),因此操作(Action)选择算法对复杂类型无效。
一旦操作(Action)被选出,模型绑定器才会被调用。
4路由过程的扩展
接口 |
描述 |
IHttpControllerSelector |
选择控制器 |
IHttpControllerTypeResolver |
获得控制器(controller)类型列表,DefaultHttpControllerSelector会从这个列表中选择控制器(controller)类型。 |
IAssembliesResolver |
获得项目程序集列表,IHttpControllerTypeResolver 会从这个列表中找到控制器(controller)类型 |
IHttpControllerActivator |
创建新的控制器(controller)实例 |
IHttpActionSelector |
选择操作(Action) |
IHttpActionInvoker |
调用操作(Action) |
要想使用自定义的上述接口实现,那么要注册服务。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//其他配置
config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector());
}
}
例:扩展IHttpControllerSelector
实现GetControllerMapping和SelectController方法,GetControllerMapping为发现系统所有可能的控制器(controller),SelectController会使用这些所有可能的控制器(controller),因此需要CustomHttpControllerSelector的属性存储所有可能的控制器(controller)。具体示例见“ASP.NET Web API编程——版本控制”
public class CustomHttpControllerSelector : IHttpControllerSelector
{ public IDictionary<string, System.Web.Http.Controllers.HttpControllerDescriptor> GetControllerMapping()
{
throw new NotImplementedException();
} public System.Web.Http.Controllers.HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
throw new NotImplementedException();
}
}
此外,有时扩展Web API框架的DefaultHttpControllerSelector或许是更加合理的方式。
例:扩展IAssembliesResolver,动态加载控制器(controller)。
可以将控制器(controller)类单独编制为一个dll,放在指定的文件夹内,这样无需编译整个框架,就能修改控制器(controller)。
public class ServiceAssembliesResolver : IAssembliesResolver
{
private string path;
public ServiceAssembliesResolver(string path)
{
this.path = path;
}
public ICollection<System.Reflection.Assembly> GetAssemblies()
{
List<Assembly> assemblies = new List<Assembly>();
try
{
//初始化
assemblies = new List<Assembly>();
//加载每一个服务插件
foreach (string file in Directory.GetFiles(path, "*.dll"))
{
var controllersAssembly = Assembly.LoadFrom(file);
assemblies.Add(controllersAssembly);
}
}
catch (Exception ex)
{
//处理异常
}
return assemblies;
}
}
此外继承Web API框架默认的DefaultAssembliesResolver也是一个好办法。
public class ServiceAssembliesResolver : DefaultAssembliesResolver
{
//服务插件路径
private string path;
public ServiceAssembliesResolver(string path):base()
{
this.path = path;
}
public override ICollection<Assembly> GetAssemblies()
{
List<Assembly> assemblies = new List<Assembly>();
try
{
//获得已有的服务
ICollection<Assembly> baseAssemblies = base.GetAssemblies();
//初始化
assemblies = new List<Assembly>(baseAssemblies);
//加载每一个服务插件
foreach (string file in Directory.GetFiles(path, "*.dll"))
{
var controllersAssembly = Assembly.LoadFrom(file);
assemblies.Add(controllersAssembly);
}
}
catch (Exception ex)
{
//处理异常
}
return assemblies;
}
}
5使用特性设置路由
为了更好地支持URI参数,所以使用路由特性。
5.1使用特性
RouteAttribute
路由特性定义为:
public sealed class RouteAttribute : Attribute, IDirectRouteFactory, IHttpRouteInfoProvider
{
public RouteAttribute();
//template:描述要匹配的 URI 模式的路由模板
public RouteAttribute(string template);
//路由名称
public string Name { get; set; }
//路由顺序
public int Order { get; set; }
//描述要匹配的 URI 模式的路由模板
public string Template { get; }
}
RoutePrefix
使用RoutePrefix特性为整个控制器(controller)设置路由前缀,路由前缀特性定义为:
public class RoutePrefixAttribute : Attribute, IRoutePrefix
{
protected RoutePrefixAttribute();
//prefix: 控制器的路由前缀。
public RoutePrefixAttribute(string prefix);
//获取路由前缀。
public virtual string Prefix { get; }
}
例子:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
//GET api/values/getvalues
[Route("getvalues")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
使用“~”可重写路由前缀,例如:
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
[Route("~/api/allvalues")]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
路由前缀可以包含参数:
[RoutePrefix("api/values/{id}")]
public class ValuesController : ApiController
{
//GET api/values/1/getvalues
[Route("getvalues")]
public IEnumerable<string> Get(string id)
{
//
}
}
路由约束
限制参数的类型,语法为:{parameter:constraint},可以指定多个约束,每个约束用:分隔。Route和RoutePrefix特性均支持这种用法。
[RoutePrefix("api/values/{id:int:min(1)}")]
public class ValuesController : ApiController
{
[Route("GetValues")]
public IEnumerable<string> Get(int id)
{
//具体实现
}
}
约束规则如下:
约束 |
描述 |
例子 |
alpha |
匹配大写或小写拉丁字母(A-Z,a-z) |
{x:alpha} |
bool |
匹配Boolean 类型 |
{x:bool} |
datetime |
匹配DateTime 类型 |
{x:datetime} |
decimal |
匹配decimal类型 |
{x:decimal} |
double |
匹配double类型 |
{x:double} |
float |
匹配float类型 |
{x:float} |
guid |
匹配GUID值 |
{x:guid} |
int |
匹配int类型 |
{x:int} |
length |
匹配指定长度或指定长度范围内的字符串 |
{x:length(6)} {x:length(1,20)} |
long |
匹配long类型 |
{x:long} |
max |
匹配整型,其值不能大于设置的值 |
{x:max(10)} |
maxlength |
匹配字符串,它的长度不能超过设定的值 |
{x:maxlength(10)} |
min |
匹配整型,其值不能小于设定的值 |
{x:min(10)} |
minlength |
匹配字符串,它的长度不能小于设置的值 |
{x:minlength(10)} |
range |
指定整型的范围 |
{x:range(10,50)} |
regex |
匹配正则表达式 |
{x:regex(^\d{3}-\d{3}-\d{4}$)} |
可选URI参数与默认值
使用?来标识路由值为可选的,同时必须为操作参数设置默认值。
例:
[Route("api/v1/user/{id:int?}")]
[HttpGet]
public IHttpActionResult User(int id=)
{
return Json("id:"+id);
}
设置路由名称
设置路由名称后,可以在使用控制器(controller)的属性ApiController.Url或ApiController.Route拼接URL。
例:在GetPublicationNew中获得路由到操作GetPublication的URL
[Route("api/v1/publication",Name="V1Publication")]
public IHttpActionResult GetPublication()
{
return Json("api/v1/publication");
} [HttpGet]
[Route("api/v2/publication")]
public IHttpActionResult GetPublicationNew()
{
string url = Url.Link("V1Publication", null);
return Json(url);
}
路由顺序
RouteOrder值较小的路由先被使用,默认的RouteOrder值为0。
比较顺序的规则为:
1)先比较RouteOrder的值
2)查看路由模板的URI参数,对于每一个参数,由参数决定的顺序为:
- 字面值顺序排第一。
- 含有路由约束的顺序排第二。
- 没有路由约束的顺序排第三。
- 含有通配符和路由约束的顺序排第四。
- 含有通配符和无路由约束的顺序排第五。
3)在上述规则无法区分的情况下,即上述规则判定顺序相同的两个路由,决定顺序的依据是:不区分大小写地,比较字符串的序号。
例:这里引用官网文档的例子
(https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2)
[RoutePrefix("orders")]
public class OrdersController : ApiController
{
[Route("{id:int}")] // 设置路由约束
public HttpResponseMessage Get(int id) { ... } [Route("details")] // 字面值
public HttpResponseMessage GetDetails() { ... } [Route("pending", RouteOrder = )]//指定路由顺序
public HttpResponseMessage GetPending() { ... } [Route("{customerName}")] //无路由约束
public HttpResponseMessage GetByCustomer(string customerName) { ... } [Route("{*date:datetime}")] // 含有通配符
public HttpResponseMessage Get(DateTime date) { ... }
}
路由顺序依次为:
第一.orders/details
第二.orders/{id}
第三.orders/{customerName}
第四.orders/{*date}
第五.orders/pending
使路由特性起作用
要想使路由特性起作用,必须在WebApiConfig.Register方法中加入代码:config.MapHttpAttributeRoutes();
如下完整代码:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//启用路由特性
config.MapHttpAttributeRoutes(); // 其他配置
}
}
可以同时使用路由特性与基于协定路由:
public static void Register(HttpConfiguration config)
{
//启用路由特性
config.MapHttpAttributeRoutes(); // 基于协定的路由
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
自定义路由约束
实现一个继承自IHttpRouteConstraint接口的类,然后注册此类。
例:
自定义CustomHttpRouteConstraint
public class CustomHttpRouteConstraint : IHttpRouteConstraint
{ public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
//实现验证过程
}
}
注册CustomHttpRouteConstraint,为这个约束提供一个简称。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//其他配置 var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("customcons", typeof(CustomHttpRouteConstraint)); config.MapHttpAttributeRoutes(constraintResolver);
}
}
使用自定义约束
[Route("{name:customcons}")]
public IHttpActionResult SUser(string name)
{
return Json("name:" + name);
}
5.2应用场景:
支持多版本API:
假设随着业务的扩展,对API接口进行升级改造,老的接口还要使用一段时间而不会立即停用,这时需要版本控制机制。如下面的例子,使用路由特性后,
虽然URI片段中的指定的操作(Action)名称一样,但是调用的操作(Action)却不一样。
例:
[Route("api/v1/publication")]
public IHttpActionResult GetPublication()
{
return Json("api/v1/publication");
} [Route("api/v2/publication")]
public IHttpActionResult GetPublicationNew()
{
return Json("api/v2/publication");
}
当在浏览器中输入:http://localhost:45778/api/v1/publication时,显示"api/v1/publication"
当在浏览器中输入:http://localhost:45778/api/v2/publication时,显示"api/v2/publication"
由于上述操作定义在同一个控制器(Controller)类中,所以方法名不能相同。
注意:由于上述操作名称中含有Get字符串,所以支持Get请求。
重载
为了支持重载的方法,使用路由特性
例:
[Route("api/v1/user/{id}")]
public IHttpActionResult GetUser(int id)
{
return Json("id:"+id);
} [Route("api/v2/user/{name}")]
public IHttpActionResult GetUser(string name)
{
return Json("name:" + name);
}
当在浏览器中输入http://localhost:45778/api/v1/user/1时,页面显示“id:1”
当在浏览器中输入http://localhost:45778/api/v2/user/coding时,页面显示“name:coding”
支持URI时间参数
例:
请求Url:http://localhost:45778/api/user/1982-02-01
[HttpGet]
[Route("api/user/{time:datetime}")]
public IHttpActionResult User(DateTime time)
{
return Json("time:" + time);
}
输出为:"time:1982/2/1 0:00:00"
请求Url:http://localhost:45778/api/user/1982/02/01
[HttpGet]
[Route("api/user/{*time:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]
public IHttpActionResult User(DateTime time)
{
return Json("time:" + time);
}
输出为:"time:1982/2/1 0:00:00"
也可以将两种约束一起使用,这样可以同时支持两种格式了
[HttpGet]
[Route("api/user/{*time:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]
[Route("api/user/{time:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
public IHttpActionResult User(DateTime time)
{
return Json("time:" + time);
}
参考
https://docs.microsoft.com/en-us/aspnet/web-api/
转载与引用请注明出处。 时间仓促,水平有限,如有不当之处,欢迎指正。
ASP.NET Web API编程——路由的更多相关文章
- ASP.NET MVC , ASP.NET Web API 的路由系统与 ASP.NET 的路由系统是怎么衔接的?
ASP.NET MVC 的路由实际上是建立在 ASP.NET 的路由系统之上的. MVC 路由注册通常是这样的: RouteTable 是一个全局路由表, 它的 Routes 静态属性是一个 Ro ...
- 2.3属性在 ASP.NET Web API 2 路由
路由是 Web API 如何匹配 URI 的行动.Web API 2 支持一种新型的路由,称为属性路由.顾名思义,属性路由使用属性来定义路由.属性路由给你更多的控制 Uri 在您的 web API.例 ...
- [翻译]ASP.NET Web API的路由
原文:Routing in ASP.NET Web API 在我们新建一个Web API项目时,会在App_Start文件夹下的WebApiConfig.cs中定义一个默认路由: config.Rou ...
- ASP.NET Web API编程——构建api帮助文档
1 概要 创建ASP.NET Web Api 时模板自带Help Pages框架. 2 问题 1)使用VS创建Web Api项目时,模板将Help Pages框架自动集成到其中,使得Web Api项目 ...
- ASP.NET Web API编程——序列化与内容协商
1 多媒体格式化器 多媒体类型又叫MIME类型,指示了数据的格式.在HTTP协议中多媒体类型描述了消息体的格式.一个多媒体类型包括两个字符串:类型和子类型. 例如: text/html.image/p ...
- ASP.NET Web API编程——客户端调用
可以使用HttpClient这个调用Web API,下面是HttpClient的定义,列举了一些常用的方法,其中还有一些没有列举,包括重载的方法. public class HttpClient : ...
- 2.4使用属性在 ASP.NET Web API 2 路由创建一个 REST API
Web API 2 支持一种新型的路由,称为属性路由.属性路由的一般概述,请参阅属性路由 Web API 2 中.在本教程中,您将使用属性路由创建一个 REST API 集合的书.API 将支持以下操 ...
- ASP.NET Web API编程——模型验证与绑定
1.模型验证 使用特性约束模型属性 可以使用System.ComponentModel.DataAnnotations提供的特性来限制模型. 例如,Required特性表示字段值不能为空,Range特 ...
- ASP.NET Web API编程——版本控制
版本控制 版本控制的方法有很多,这里提供一种将Odata与普通web api版本控制机制统一的方法,但也可以单独控制,整合控制与单独控制主要的不同是:整合控制通过VersionController ...
随机推荐
- 开发中使用Gson的实例(时间格式错误解决方法)
...... // 通过GSON解析,使用4个实体类来接受(TotalResponse.TradeRateResponse.TradeRatess.TbTradeRates) GsonBuilder ...
- golang channel的使用以及调度原理
golang channel的使用以及调度原理 为了并发的goroutines之间的通讯,golang使用了管道channel. 可以通过一个goroutines向channel发送数据,然后从另一个 ...
- JavaScript 变量屏蔽
不同作用域中相同名称的变量就会触发变量屏蔽: { let x = {color:"blue"}; let y = x; let z = 3; { //重复定义x,所以对全局变量x进 ...
- python中Django 使用方法简述
Django是由Python写成的免费而且开源的Web应用框架--一堆零件的组成,可以帮助我们轻松的开发网站.这些零件都包括常用的:登录(注册,登入,登出),网站后台管理,表单,文件上传等.可以帮助我 ...
- jquery中的ajax方法参数
引用来自:http://www.cnblogs.com/tylerdonet/p/3520862.html jquery中的ajax方法参数总是记不住,这里记录一下. 1.url: 要求为String ...
- SparkHiveContext和直接Spark读取hdfs上文件然后再分析效果区别
最近用spark在集群上验证一个算法的问题,数据量大概是一天P级的,使用hiveContext查询之后再调用算法进行读取效果很慢,大概需要二十多个小时,一个查询将近半个小时,代码大概如下: try: ...
- Python函数学习——递归
递归函数 在函数内部,可以调用其他函数.如果一个函数在内部调用自身本身,这个函数就是递归函数. 函数实现过程 def calc(n): v = int(n//2) print(v) if v > ...
- mysql 数据库基本命令语句
mysql mariadb 客户端连接 mysql -uroot -p; 客户端退出exit 或 \q 显示所有数据库show databases;show schemas; 创建数据库create ...
- Linux源码-等待队列注释
等待队列 Linux中了等待队列的毒,代码中充斥着等待队列.不信你翻翻代码. 等待队列的唤醒我们这里叫激活.免得和线程唤醒混淆. 数据结构 头结点wait_queue_head_t的结构 struct ...
- 最大堆(Java数组实现)
最大堆 data[1]开始存,data[0]空着不用.也可以把data[0]当成size来用. public class MaxHeap<T extends Comparable<? su ...