作者:网络 时间: 2020-10-16
在 MVC 里面,默认路由机制是通过 url 路径去匹配对应的 action 方法,比如/Home/GetUser 这个 url,就表示匹配 Home 这个 Controller 下面的 GetUser 方法,这个很好理解,因为在 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 = "Department", action = "Index", id = UrlParameter.Optional }
);
}
}
url: "{controller}/{action}/{id}"这个定义了我们 url 的规则,{controller}/{action}定义了路由的必须参数,{id}是可选参数
和 MVC 里面的路由有点不同,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 的名称,那么如何找到请求的方法呢?我们先来简单看一个例子:
public class OrderController : ApiController
{
[HttpGet]
public object GetAll()
{
return "Success";
}
}
我们通过 url 来访问
说明请求能够成功。
为什么这个请求能够成功呢?那是因为,当我们访问http://localhost:21528/api/Order这个路径的时候,webapi的路由引擎会自动去匹配"api/{controller}/{id}"这个模板,于是找到了控制器是Order这个,那么问题来了?它是如何定位到GetAll()这个方法的呢?这里就是和MVC不同的地方,前面说过,Webapi的路由规则是通过http方法去匹配对应的action,那么,我们通过浏览器访问http://localhost:21528/api/Order这个路径的时候,浏览器默认通过url访问的都是get请求,于是webapi的路由引擎就会去找Order这个控制器里面的get请求的方法,由于没有参数,所以自动匹配到了无参数的get请求→GetAll()方法,所以请求成功!
当然,WebApi 也支持 MVC 里面的路由机制,但 RestFul 风格的服务要求请求的 url 里面不能包含 action,所以,在 WebApi 里面是并不提倡使用 MVC 路由机制的。
这是一个最简单的例子,下面我们就来详细看看 WebApi 里面的路由原理以及使用。
上面我们提到了,新建一个 WebApi 服务的时候,会自动在 WebApiConfig.cs 文件里面生成一个默认路由:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
将 MapHttpRoute()方法转到定义可以,它有四个重载方法:
分别来看看各个参数的作用:
public class OrderController : ApiController
{
[HttpGet]
public object GetAll()
{
return "Success";
}
[HttpGet]
public object GetById(int id)
{
return "Success" + id ;
}
}
我们通过http://localhost:21528/api/Order/2来访问,得到结果:
我们再通过http://localhost:21528/api/Order/a来访问,得到结果:
这个是很好理解的,id 的值不匹配正则表达式。
而我们访问http://localhost:21528/api/Order。结果:
竟然连 GetAll()方法都找不到了。这是为什么呢?原来就是这个约束在作怪,正则\d+表示匹配一个或多个数字,所以如果请求的 url 里面没有传数字,则自动匹配不到。所以,如果需要匹配无参的方法,我们把约束改成这样: constraints: new { id = @"\d*" } ,这个表示匹配 0 个或多个数字,再来试试
这样就 OK 了。
上述说了那么多都是约束 id 的,其实你也可以使用表达式去约束 controller、action 等等,但一般不常用,我们就不做过多讲解。
上面介绍了这么多,都是关于默认路由原理的介绍。除了默认路由,我们也可以自定义路由,我们将 WebApiConfig.cs 里面改成这样:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 路由
config.MapHttpAttributeRoutes();
//1.默认路由
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//2.自定义路由一:匹配到action
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "actionapi/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
//3.自定义路由二
config.Routes.MapHttpRoute(
name: "TestApi",
routeTemplate: "testapi/{controller}/{ordertype}/{id}",
defaults: new { ordertype="aa", id = RouteParameter.Optional }
);
}
}
除了默认路由,我们再加入另外两个自定义路由规则
第一个自定义路由很好理解,和 MVC 里面的路由机制保持一致,只不过为了区别默认路由,我们将路由模板的前缀改成了“actionapi”。我们通过这个自定义的路由也能找到匹配的方法。
比如我们访问http://localhost:21528/actionapi/Order/GetAll,得到结果:
通过 action 的名称来匹配很好理解,上面的 GetAll()是方法名,webApi 会默认它就是 action 的名称,如果你想要方法名和 action 的名称不一致,你也可以自定义 action 的名称,这个可以通过特性 ActionName 来实现,如下:
[ActionName("TestActionName")]
[HttpGet]
public object GetById(int id)
{
return "Success" + id ;
}
测试结果:
之前博主演示参数和返回值的时候都是使用的匹配到 action 的路由。这种用法和 MVC 里面保持一致,比较好理解,但是 WebApi 里面并不提倡。
第二个自定义路由第一眼看上去是不太好理解的,没关系,我们先来按照它的路由模板规则使用试试。
通过http://localhost:21528/testapi/Order/aa/匹配到GetAll()方法
通过http://localhost:21528/testapi/Order/aa/2匹配到的是GetById()方法
通过http://localhost:21528/testapi/Order/bb/2匹配到的也是GetById()方法。
什么意思呢?也就是说,只要{ordertype}按照路由规则去配置,都能找到对应的方法。这里的{ordertype}有什么用呢?这个要留在下面介绍特性路由的时候来解释。
有了上面的这些理论作为基础,我们再来分析下 WebApi 里面路由机制的原理以及路由匹配的过程。由于 WebApi 的路由机制和 MVC 有许多的相似性,所以要想理解 Webapi 的路由机制,有需要搬出来那些 asp.net Rounting 里面的对象。这个过程有点复杂,博主就根据自己的理解,提提一些主要的过程:
1、WebApi 服务启动之后,会执行全局配置文件 Global.asax.cs 的 protected void Application_Start(){GlobalConfiguration.Configure(WebApiConfig.Register);} 方法,通过参数委托执行 WebApiConfig.cs 里面的 public static void Register(HttpConfiguration config) 这个方法,将所有配置的路由信息添加到 HttpRouteCollection 对象中(MVC 里面可能是 RoutCollection 对象)保存起来。这里的 HttpRoutCollection 对象的实例名是 Routes,这个很重要,后面要用到。
2、当我们发送请求到 WebApi 服务器的时候,比如我们访问http://localhost:21528/api/Order这个url的时候,请求首先还是会被UrlRoutingModule监听组件截获,然后,将截获的请求在Routes路由集合中匹配到对应的路由模板(如果匹配不到对应的路由模板,则返回404),得到对应的IHttpRoute对象。IHttpRoute对象是Routes集合里面匹配到的一个实体。
3、将 IHttpRoute 对象交给当前的请求的上下文对象 RequestContext 处理,根据 IHttpRoute 对象里面的 url 匹配到对应的 controller,然后再根据 http 请求的类型和参数找到对应的 action。这样一个请求就能找到对应的方法了。
这个过程本身是非常复杂的,为了简化,博主只选择了最主要的几个过程。更详细的路由机制可以参考:http://www.cnblogs.com/wangiqngpei557/p/3379095.html。这文章写得有点深,有兴趣的可以看看。
通过上文路由的过程,我们知道,一个请求过来之后,路由主要需要经历三个阶段
这点上面已经说了很多了,主要就是路由模板的配置和 url 的匹配。在此不作过多说明。
如果你反编译路由模块的代码,你会发现控制器的选择主要在 IHttpControllerSelector 这个接口的 SelectController()方法里面处理。
该方法将当前的请求以 HttpRequestMessage 对象作为参数传入,返回 HttpControllerDescriptor 对象。这个接口默认由 DefaultHttpControllerSelector 这个类提供实现
默认实现的方法里面大致的算法机制是:首先在路由字典中找到实际的控制器的名称(比如“Order”),然后在此控制器名称上面加上字符串“Controller”的到请求控制器的全称(比如“OrderController”),最后找到对应的 WebApi 的 Controller,实例化就得到当前请求的控制器对象。
得到了控制器对象之后,Api 引擎通过调用 IHttpActionSelector 这个接口的 SelectAction()方法去匹配 action。这个过程主要包括:
如果路由模板配置了{action},那么找到对应的 action 就很简单,如果没有配置 action,则会首先匹配请求类型(get/post/put/delete 等),然后匹配请求参数,找到对应的 action。我们看个例子,比如,我们的 controller 加如下一些方法。
public class OrderController : ApiController
{
[HttpGet]
public IHttpActionResult GetAll()
{
return Ok<string>("Success");
}
[HttpGet]
public IHttpActionResult GetById(int id)
{
return Ok<string>("Success" + id );
}
[HttpPost]
public HttpResponseMessage PostData(int id)
{
return Request.CreateResponse();
}
[HttpPost]
public HttpResponseMessage SavaData(ORDER order)
{
return Request.CreateResponse();
}
[HttpPut]
public IHttpActionResult Put(int id)
{
return Ok();
}
[HttpDelete]
public IHttpActionResult DeleteById(int id)
{
return Ok();
}
}
匹配 action 的结果
url | http 方法 | 参数 | 结果 |
---|---|---|---|
http://localhost:21528/api/Order | get | none | 匹配 GetAll 方法 |
http://localhost:21528/api/Order | get | id | 匹配 GetById 方法 |
http://localhost:21528/api/Order | post | order | 匹配 SavaData 方法 |
http://localhost:21528/api/Order | put | id | 匹配 Put 方法 |
http://localhost:21528/api/Order | delete | id | 匹配 DeleteById 方法 |
WebApi 还提供了一个 action 同时支持多个 http 方法的请求,使用 AcceptVerbs 特性去标记。但博主觉得实际使用并不多,有兴趣的可以了解下。
[AcceptVerbs("GET", "POST")]
public IHttpActionResult GetById(int id)
{
return Ok<string>("Success" + id );
}
上面说了这么多都是路由的一些全局配置。并且存在问题:
如果 http 请求的方法相同(比如都是 post 请求),并且请求的参数也相同。这个时候似乎就有点不太好办了,这种情况在实际项目中还是比较多的。比如
public class OrderController : ApiController
{
//订单排产
[HttpPost]
public void OrderProduct([FromBody]string strPostData)
{
}
//订单取消
[HttpPost]
public void OrderCancel([FromBody]string strPostData)
{
}
//订单删除
[HttpPost]
public void OrderDelete([FromBody]string strPostData)
{
}
}
这个时候如果使用我们上面讲的 Restful 风格的路由是解决不了这个问题的。当然,有园友可能就说了,既然这样,我们在路由模板里面加上“{action}”不就搞定了么!这样确实可行。但还是那句话,不提倡。我们来看看如何使用特性路由解决这个问题。
如果要使用特性路由,首先在 WebApiConfig.cs 的 Register 方法里面必须先启用特性路由:
public static void Register(HttpConfiguration config)
{
// 启用Web API特性路由
config.MapHttpAttributeRoutes();
//1.默认路由
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
一般情况下,当我们新建一个 WebApi 项目的时候,会自动在 Register 方法里面加上这句话。
我们在 OrderController 这个控制器里面加这个 action
[Route("Order/SaveData")]
[HttpPost]
public HttpResponseMessage SavaData(ORDER order)
{
return Request.CreateResponse();
}
然后我们通过 Web 里面的 Ajax 调用
$(function () {
$.ajax({
type: 'post',
url: 'http://localhost:21528/Order/SaveData',
data: { ID: 2, NO:"aaa"},
success: function (data, status) {
alert(data);
}
});
});
得到结果:
当然,有人可能就有疑义了,这个特性路由的作用和“{action}”的作用一样嘛,其实不然,如果这里改成 [Route("Test/AttrRoute")] ,然后请求的 url 换成http://localhost:21528/Test/AttrRoute,一样能找到对应的action。
特性路由的目的是为了解决我们公共路由模板引擎解决不了的问题。一个 action 定义了特性路由之后,就能通过特性路由上面的路由规则找到。
特性路由的规则可以使用“{}”占位符动态传递参数,比如我们有这样一个特性路由
[Route("ordertype/{id}/order")]
[HttpGet]
public IHttpActionResult GetById(int id)
{
return Ok<string>("Success" + id );
}
在浏览器里面调用
调用成功。到此,我们就能看懂本文最开始那个看似“怪异”的路由 →/api/user/1/detail 这个了。
[Route("api/order/{id:int=3}/ordertype")]
[HttpGet]
public IHttpActionResult GetById(int id)
{
return Ok<string>("Success" + id );
}
这里约束可变部分{id}的取值必须是 int 类型。并且默认值是 3.
看看效果
不满足约束条件,则直接返回 404。
在正式项目中,同一个控制器的所有的 action 的所有特性路由标识一个相同的前缀,这种做法并非必须,但这样能够增加 url 的可读性。一般的做法是在控制器上面使用特性[RoutePrefix]来标识。
[RoutePrefix("api/order")]
public class OrderController : ApiController
{
[Route("")]
[HttpGet]
public IHttpActionResult GetAll()
{
return Ok<string>("Success");
}
[Route("{id:int}")]
[HttpGet]
public IHttpActionResult GetById(int id)
{
return Ok<string>("Success" + id );
}
[Route("postdata")]
[HttpPost]
public HttpResponseMessage PostData(int id)
{
return Request.CreateResponse();
}
}
那么这个这个控制器的 action 的时候,都需要/api/order 开头,后面接上 action 特性路由的规则。
通过以上,我们就可以构造一个 Restful 风格的 WebApi 服务。
[RoutePrefix("api/AttrOrder")]
public class OrderController : ApiController
{
[Route("")]
[HttpGet]
public IHttpActionResult GetAll()
{
return Ok<string>("Success");
}
[Route("{id:int=3}/OrderDetailById")]
[HttpGet]
public IHttpActionResult GetById(int id)
{
return Ok<string>("Success" + id );
}
[Route("{no}/OrderDetailByNo")]
[HttpGet]
public IHttpActionResult GetByNO(string no)
{
return Ok<string>("Success" + no);
}
[Route("{name}/OrderDetailByName")]
[HttpGet]
public IHttpActionResult GetByName(string name)
{
return Ok<string>("Success" + name);
}
[Route("postdata")]
[HttpPost]
public HttpResponseMessage PostData(int id)
{
return Request.CreateResponse();
}
[Route("Test/AttrRoute")]
[HttpPost]
public HttpResponseMessage SavaData(ORDER order)
{
return Request.CreateResponse();
}
}
得到结果
整了这么久终于整完了。如果你觉得本文对你有帮助,请帮忙博主推荐,您的支持是博主最大的动力!