一、MVC和WebApi路由机制比较
1、MVC里面的路由
在MVC里面,默认路由机制是通过url路径去匹配对应的action方法,比如/Home/Index这个url,就表示匹配Home这个Controller下面的Index方法,这个很好理解,因为在MVC里面定义了一个默认路由,在App_Start文件夹下面有一个RouteConfig.cs文件
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
url: "{controller}/{action}/{id}"这个定义了我们url的规则,{controller}/{action}定义了路由的必须参数,{id}是可选参数
2、WebApi里面的路由
WebApi的默认路由是通过http的方法(get/post/put/delete)去匹配对应的action,也就是说webapi的默认路由并不需要指定action的名称。还是来看看它的默认路由配置,我们新建一个Webapi项目,在App_Start文件夹下面自动生成一个WebApiConfig.cs文件:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 路由
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
和MVC类似,routeTemplate: "api/{controller}/{id}"这个定义了路由的模板,api/{controller}是必选参数,{id}是可选参数
那么问题就来了,如果我们的url不包含action的名称,那么如何找到请求的方法呢?
eg:
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
public string Get(int id)
{
return "value";
}
// POST api/values
public void Post([FromBody]string value)
{
}
// PUT api/values/5
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
public void Delete(int id)
{
}
}
通过浏览器访问url
//浏览器返回
<ArrayOfstring xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<string>value1</string>
<string>value2</string>
</ArrayOfstring>
Webapi的路由规则是通过http方法去匹配对应的action,浏览器默认通过url访问的都是get请求,webapi的路由引擎就会去找控制器里面的get请求的方法,由于没有参数,所以自动匹配到了无参数的get请求→Get()方法
++WebApi也支持MVC里面的路由机制,但RestFul风格的服务要求请求的url里面不能包含action,所以,在WebApi里面是并不提倡使用MVC路由机制的++
特性路由
WebApi2默认的路由规则我们称作基于约定路由,很多时候我们使用RESTful风格的URI.简单的路由是没问题的,如 api/Products/{id},但有些事很难处理的,如资源之间存在嵌套关系:客户包含订单,书有作者属性等等。对于这种Uri,我们希望的路由是这样的:/costomers/{customerid}/orders 或 /costomers/{customerid}/orders/{orderid}
通过使用特性路由,我们还可以做API的版本控制
public class ValuesController : ApiController
{
[Route("api/Values/{id}/V1")]
public string Get(int id)
{
return $"value {id}";
}
[Route("api/Values/{id}/V2")]
public string GetV2(int id)
{
return $"value V2 {id}";
}
}
//~/api/Values/1/v1返回
<string>value 1</string>
//~/api/Values/1/v2返回
<string>value V2 1</string>
如果要启用特性路由,需要改成如下代码
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
//...
GlobalConfiguration.Configure(WebApiConfig.Register);
//...
}
}
启用特性路由还需要在配置过程中调用System.Web.HttpConfigurationExtensions类的MapHttpAttributeRoutes方法
using System.Web.Http;
namespace WebApplication
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API routes
config.MapHttpAttributeRoutes();
// Other Web API configuration not shown.
}
}
}
默认情况,WebApi会根据action的方法名前缀查找action(不区分大小写),比如GetUsers,会匹配Get。通过在action上添加HttpMethod特性,可以覆盖action需要映射的Http Method。
可使用的特性包括:[HttpDelete],[HttpPost],[HttpHead],[HttpOptions],[HttpPatch],[HttpGet],[HttpPut]
配置好特性路由,开始使用
public class DefaultController : ApiController
{
[Route("api/Default/{id:int}/{name}")]
public string GetUser(int id,string name)
{
return $"{nameof(GetUser)} id:{id} name:{name}";
}
[Route("")]
[Route("api")]
[Route("api/Default")]
public string Get()
{
return $"Get value {DateTime.Now}";
}
}
在方法上标记Route 即指定方法路由规则,也可以在一个方法上标记多个路由规则
//http://localhost:24727 http://localhost:24727/api http://localhost:24727/api/default 访问的都是DefaultController控制器下的Get()方法
<string>Get value 2020/4/29 14:23:22</string>
//http://localhost:24727/api/default/1/aaa 访问DefaultController控制器下的GetUser()方法
<string>GetUser id:1 name:aaa</string>
路由约束
路由约束让我们可以限制模板参数的匹配方式。一般的语法是 "{参数:约束类型}":
约束 | 介绍 | 示例 |
---|---|---|
alpha | 匹配大写或小写字母 (a-z, A-Z) | {x:alpha} |
bool | {x:bool} | |
datetime | {x:datetime} | |
decimal | {x:decimal} | |
double | {x:double} | |
float | 匹配一个 32位浮点数 | {x:float} |
guid | {x:guid} | |
int | {x:int} | |
length | 匹配一个长度在指定范围内的字符串 | {x:length(6)} {x:length(1,20)} |
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}$)} |
如果要指定多个约束,需要用冒号间隔 [Route("api/Default/{id:int:max(10)}")]
[HttpGet]
[Route("api/Default/{id:int:max(10)}")]
public string MultipleConstraints(int id)
{
return $"{nameof(MultipleConstraints)} id:{id} {DateTime.Now}";
}
//http://localhost:24727/api/default/10 这里的id只能传小于10的整数 否则会报错找不到方法
<string>MultipleConstraints id:10 2020/4/29 14:43:16</string>
在参数约束后面添加一个问号,可以设定URI参数是可选的;
[HttpGet]
[Route("api/Default/{id:int?}")]
public string OptionalParameter(int id=10)
{
return $"{nameof(OptionalParameter)} id:{id} {DateTime.Now}";
}
//http://localhost:24727/api/default
<string>OptionalParameter id:10 2020/4/29 14:58:51</string>
也可以像普通方法那样指定默认值:
[HttpGet]
[Route("api/Default/{id:int=111}")]
public string DefaultParameter(int id)
{
return $"{nameof(DefaultParameter)} id:{id} {DateTime.Now}";
}
//http://localhost:24727/api/default
<string>DefaultParameter id:111 2020/4/29 14:56:52</string>
通常情况下,一个Controller下的action会使用相似的路由模板,这时候可以为整个controller指定[RoutePrefix]特性,以使用共同的前缀
[RoutePrefix("api/default/{id:int}")]//路由前缀中可以包含参数
public class DefaultController : ApiController
{
[HttpGet]
[Route("")]//这里的[Route("")]不能省略否则会找不到方法
public string Prefix(int id)
{
return $"{nameof(Prefix)} id:{id} {DateTime.Now}";
}
[HttpGet]
[Route("other")]
public string NotPrefix(int id)
{
return $"{nameof(NotPrefix)} id:{id} {DateTime.Now}";
}
[HttpGet]
[Route("~/api/other/{id:int}")]//如果有某个特殊路由不希望使用前缀,可以在路由中添加~/
public string Other(int id)
{
return $"{nameof(Other)} id:{id} {DateTime.Now}";
}
[HttpGet]
[Route("~/api/Date/{id:int}/{*date:datetime:regex(\d{4}/\d{2}/\d{2})}")]//有时候需要几个路由片段结合起作用,这时候就需要使用字符* 不过这种参数只能用作路由的最后一个参数
public string Date(int id,DateTime date)
{
return $"{nameof(Date)} id:{id} date:{date} {DateTime.Now}";
}
}
//http://localhost:24727/api/default/1
<string>Prefix id:1 2020/4/29 15:55:34</string>
//http://localhost:24727/api/default/1/other
<string>NotPrefix id:1 2020/4/29 15:56:00</string>
//http://localhost:24727/api/other/1
<string>Other id:1 2020/4/29 15:56:25</string>
//http://localhost:24727/api/date/1/2020/04/29
<string>
Date id:1 date:2020/4/29 0:00:00 2020/4/29 15:44:52
</string>
路由顺序
通过设定特性[Route("xxx",RouteOrder=n)]可以指定路由的查找顺序
不过意义不大,通过顺序来控制,还不如设定更好的路由来的实际,而且不至于混乱。