WebApi 2 路由机制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebApi 2 路由机制相关的知识,希望对你有一定的参考价值。

参考技术A

.net中包含的路由有两种,第一种是MVC模式的按url匹配action,第二种是WebApi模式的按http请求的方法匹配action,本文我们学习WebApi的路由模式。

首先新建一个WebApi项目,选择ASP.NET Web应用程序(.NET Framework)

这里我们首先来看WebApi的基础配置 WebApiConfig.cs ,其路径为 /App_Start/WebApiConfig.cs

首先以浏览器启动webapi项目

会发现页面显示403,这是很正常的,因为我们在新建项目的时候没有加入MVC,所以没有可视化的view页面,不过我们却拿到了这个项目的 端口号

为了更加方便直观的看我们的接口请求,我们选择postman,首先在项目新建一个 controller

通过以上的操作,我们就搭建好了一个webApi2的项目工程,后面我们就在这个工程的基础上进行webApi 2的开发和学习。

webApi的路由过程主要经历了如下三步

WebApiConfig.cs 这个文件是进行路由表的核心文件, WebApiConfig 里面只有一个方法,这个方法在 Global.asax 文件里的 Application_Start() 方法被调用, Global.asax 文件是一个全局文件,当我们网页启动时就会去执行它。 Register(HttpConfiguration config) 方法是配置WEB API路由的。

因此 在webAP工程启动的时候,会执行 Global.asax ,这个文件里面注册了 WebApiConfig.cs 的一系列初始化配置,从而实现了webapi。

WebApi 2 框架使用路由表。 并由Web API 的 Visual Studio 项目模板创建默认路由

转到 MapHttpRoute 的定义,可以看到它有4个重载

分别来看看各个参数的作用

webApi2工程自动为我们创建了默认的路由

经过上面的讲解,我们知道了匹配的URL是 api/controller/id
那么我们在实际的请求中应该如何请求

新建一个 StudentsController

依次对以下地址进行请求

总结:

默认路由的缺点

如下

添加如下的方法(action)

得到了一下的结果

我们来看看这个请求是如何进行匹配的,首先找到 /App_Start/WebApiConfig.cs

按照路由模板来看,我们请求的路由没有 action 名称,那么它是怎么进行匹配的呢?
其实当我们访问 http://localhost:65066/api/student 这个url的时候,webapi会自动去匹配 api/controller/id 这个模板,在这个路径中,student是controller,那么它又是怎么去找到 getStudentName 这个action的呢?明明我们都没有传action这个参数,其实Webapi的路由规则是通过http方法去匹配对应的action,我们请求这个地址是用的 GET 方法,那么webapi会找Order这个控制器里面的get请求的方法,同事我们的这个 getStudentName 是以 get 开头的,它符合了webapi的匹配规则,于是就请求成功了,但前提是你写的方法必须是以get开头的,如果当前这个controller一个get开头的都没有,那么就显示 请求的资源不支持 http 方法“GET”

当然不以get开头的前提是你必须加上 [HttpGet] 这个特性,webapi才知道你这个是get的请求方法,就能正确进行匹配。

然后我们再回到 /App_Start/WebApiConfig.cs ,看下里面 MapHttpRoute 的各个参数的含义,首先转到它的定义,发现它是 HttpRouteCollection 的扩展方法, MapHttpRoute 有4类重载

在 WebApiConfig.cs 里面修改为如下的路由模板

接口请求如下

通过 action 的名称来匹配很好理解,上面的 StudentName() 是方法名, webApi 会默认它就是 action 的名称,如果你想要方法名和 action 的名称不一致,你也可以自定义 action 的名称,这个可以通过特性 ActionName 来实现,如下:

首先看路由模板

对于同请求类型,同请求参数的请求,会出现 不传参数,找不到匹配的资源 传了参数,但是找到了与该请求匹配的多个操作 的问题,如下所示

解决以上的办法有两种

如果要使用特性路由,首先在 WebApiConfig.cs 的 Register 方法里面必须先启用特性路由(一般情况下,当我们新建一个 WebApi 项目的时候,会自动在 Register 方法里面加上这句话。)

特性路由的目的是为了解决我们公共路由模板引擎解决不了的问题。一个action定义了特性路由之后,就能通过特性路由上面的路由规则找到。
只要出现了特性路由,匹配的规则是按特性路由来的
修改studentController如下

参考

参考

路由前缀的一般的做法是在控制器上面使用特性 [RoutePrefix] 来标识。
但是需要注意路由前缀不能以 / 开头

WebApi-路由机制

一、WebApi路由机制是什么?

  路由机制通俗点来说:其实就是WebApi框架将用户在浏览器中输入的Url地址和路由表中的路由进行匹配,并根据最终匹配的路由去寻找并匹配相应的Controller和Action并执行的一个过程。

从WebApi框架接收到来自外部环境的接口调用请求到指定接口的执行大概需要以下的匹配过程:1、匹配URL路由 2、Controller匹配 3、Action匹配

下面我么分别对这几个流程进行详细说明。

 

二、匹配URL路由

  WebApi框架接收到来自外部的接口请求后,首先将路由表中的路由一条一条和Url地址进行匹配,一旦匹配上则不再继续往下匹配,如果匹配完了所有路由都未匹配上,则会报404错误。

如果成功匹配上指定路由,则框架根据路由的Url模板中指定的Controller的所在位置以及Action的所在位置从用户请求的Url中提取出将要调用的Controller的名称以及Action的名称,当然要被调用的Controller名或者Action名也可能不是从用户发起的接口调用请求Url中获得的,因为WebApi中在进行路由配置时提供了参数默认值的配置,也就是说Controller名或者Action名可能来源于路由配置时的默认值。

该流程结束后:WebApi框架将会从用户请求的接口调用URL中提取出用户想调用的接口所对应的Controller 、Action 、以及用户传给指定Action的参数(即路由数据)等等,

这些数据存储在了一个字典集合中,这个路由数据字典我们可以在Action方法中通过如下方式获得:

1 IHttpRouteData routeData = Request.GetRouteData();
2 IDictionary<string, object> routeDataValues = routeData.Values;

  这里需要澄清的几点的是:

1、URL未匹配上任何路由 和  匹配上了路由但是未找到相应的Controller和Action 是两个不同的概念,IIS对这两种情况的响应是不一样的。

URL未匹配上路由:

  如果用户请求的接口的地址不能和路由表中的所有路由相匹配,IIS将直接报告404错误。

匹配上了路由但未找到相应Controller或Action:

       如果匹配上了路由但是未找到响应的Controller或Action,那么将报类似如下错误:

技术分享图片

  MessageDetail节点详细描述了是 Controller未找到还是 Action未找到。

2、由于路由表中可以配置一条或者多条路由,并且WebApi框架在匹配成功一条路由后将不再继续往下匹配,也就是说即使此时后面还有路由可以

和当前请求匹配也不会被匹配到,所以请务必注意每条路由的配置顺序,否则可能造成意想不到的结果。

如:

 1 public static void Register(HttpConfiguration config)
 2 {
 3             config.Routes.MapHttpRoute(
 4                 name: "TestRoute",
 5                 routeTemplate: "{controller}/{action}",
 6                 defaults: new { action = "Index" }
 7             );
 8             config.Routes.MapHttpRoute(
 9               name: "DefaultApi",
10               routeTemplate: "api/{controller}/{id}",
11               defaults: new { id = RouteParameter.Optional }
12             );
13 
14 }

假设我们此时代码中包含如下控制器和Action:

 1 public class ValuesController : ApiController
 2 {
 3         public string Get()
 4         {
 5             return "value";
 6         }
 7         [HttpGet]
 8         public object QueryValues(int index)
 9         {
10             string[] strs = new string[] { "张三", "李四" };
11             if (index < 0 || index >= strs.Length)
12             {
13                 return JsonConvert.SerializeObject(strs);
14             }
15             return strs[index];
16         }
17 }

我们在浏览器中输入:http://localhost:16982/api/Values,会发现总是匹配到的是TestRoute这个路由,实际上我们只是想访问Values中的无参数的Get方法。

技术分享图片

事实上,解决这个问题只需要将TestRoute路由的配置放到DefaultApi路由的后面即可。

三、Controller匹配

      经过路由匹配后,框架已经从用户请求的Url中提取到需要访问的Controller名、Action名、以及在路由模板中定义的参数所对应的值等等,此时WebApi框架从路由数据字典中获取出键为:controller的值,在这个值的基础上去找类型名为  Controller名+Controller 结尾的类,此时如果找到相应名称的Controller类,则Controller匹配成功,但此时仅仅是Controller匹配成功,最终接口能否成功调用还需取决于下一个步骤中的Action匹配。

  Controller的匹配主要由接口:IHttpControllerSelector.SelectController方法来处理的,这个是WebApi框架定义的接口,定义如下:

1 public interface IHttpControllerSelector
2 {
3     // Methods
4     IDictionary<string, HttpControllerDescriptor> GetControllerMapping();
5     HttpControllerDescriptor SelectController(HttpRequestMessage request);
6 }

  WebApi框架对控制器的匹配进行了默认的实现,类名叫做:DefaultHttpControllerSelector,这个类默认实现了查找Controller的过程,在DefaultHttpControllerSelector内部通过 

IHttpControllerTypeResolver接口加载出所有的满足条件的控制器类型,能被加载并查找的控制器类型必须满足以下条件:

1、类必须是实现了IHttpController接口

2、必须是public

3、不能是abstract类

4、类名必须以Controller结尾

最终,从这些列表中找出名称和路由数据中的Controller名同名的Controller类,并创建该类的实例对象。

当然如果我们需要有自己的控制器匹配的逻辑,我们也可以对其进行配置,通过在:/App_Start/WebApiConfig.cs类中进行配置,配置方式如下所示:

1     public static class WebApiConfig
2     {
3         public static void Register(HttpConfiguration config)
4         {
5             // 这里我们配置成使用自己写的匹配控制器的逻辑
6          GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new MyHttpControllerSelectory(GlobalConfiguration.Configuration));
7         }
8     }

虽然WebApi框架给我们提供了匹配控制器行为的扩展点,但是微软的默认实现类DefaultHttpControllerSelector基本上可以满足大多数场景了。

四、Action匹配

   路由机制找到了将要被调用的Controller类后,接下来就是在当前已经被匹配成功的控制器类下找一个合适的Action方法,并对其调用了,Action的具体匹配流程如下:

1、根据用户调用接口时的请求方式(GET/POST/DELETE/PUT/Head等等)从已经匹配的控制器类中查找是用于该种请求方式的Action方法,通过该轮匹配可能会匹配出多个符合条件的Action方法。

2、如果路由数据字典中包含键为:action的值,那么表示Action的名称必须和该字典中的actionName相一致,也就是说只有Action方法的名称和路由数据字典中的action名匹配的才算再次步骤匹配。(该步骤不一定是必须执行的,取决于被匹配的路由中是否有指定action占位符)

3、最后一步就是action的参数绑定了,action中的各个参数的值要么来源于路由Url模板中定义好的参数在Url中提取到的值,要么来源于QueryString(也就是?后面的参数值),当然这些说的只是.NET中的原生类型(包括:int/double/DateTime/TimeSpan/Guid等等)的绑定。并不包括自定义的复杂类型(如模型类),其实复杂类型的参数值的绑定默认实现方式是从请求报文提中获得的。

Action的选择由接口IHttpActionSelector.SelectAction()方法进行实现,WebApi框架对Action的匹配进行了默认的实现,默认实现类名为:ApiControllerActionSelector,

IHttpActionSelector接口定义如下:

1 public interface IHttpActionSelector
2 {
3     // Methods
4     ILookup<string, HttpActionDescriptor> GetActionMapping(HttpControllerDescriptor controllerDescriptor);
5     HttpActionDescriptor SelectAction(HttpControllerContext controllerContext);
6 }

  需要说明的是:能作为Action被执行的方法必须满足以下几点:

1、必须是public修饰的方法

2、未被[NonAction]特性修饰的方法

3、不是从ApiController类中继承过来的方法

4、控制器的构造函数,等等也不会被匹配。

Action的匹配我们也可以实现自定义匹配规则,和上面提到的自定义Controller匹配规则的配置方式类似,如下:

1 public static void Register(HttpConfiguration config)
2 {
3    GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpActionSelector), new MyApiControllerActionSelector());
4
5 }

 

  本文主要讲述了WebApi框架如何将来自用户的接口调用请求映射到具体的Controller和Action,并对其进行执行的过程,最容易让人困惑的部分或许还是Action的选择部分,

后续我们将继续讨论关于Action选择部分的具体细节以及参数绑定过程。

  

以上是关于WebApi 2 路由机制的主要内容,如果未能解决你的问题,请参考以下文章

WebApi路由机制详解

webapi简介及参数绑定

C#进阶系列——WebApi 路由机制剖析:你准备好了吗?

在路由匹配发生之前拦截所有 WebApi 调用

WebApi-控制器路由

Web API系列教程2.1 — ASP.NET Web API中的路由机制