ASP.NET Web Api第一条请求和路由

ASP.NET应用程序的生命周期

  • ASP.NET程序的生命周期开始于用户通过浏览器向Web服务器发送一个请求,ASP.NET是一个ISAPI Web 服务器的一个扩展,当服务器接收到一个请求,服务器会检查被请求文件的扩展名,通过这个扩展名决定该使用哪个ISAPI来处理该请求,然后将该请求发送到相关的ISAPI扩展。
  • 当ASP.NET程序收到第一个请求时,ApplicationManager会创建一个应用域,应用域将不同的ASP.NET和其全局变量隔离开来,在应用域内,一个HostingEnvironment的实例被创建,该对象提供了应用的一些信息,例如文件夹的名字等。ASP.NET也会变异顶级的项目,包括位于App_Code文件夹下的代码。
  • ASP.NET会创建并初始化核心的对象:HttpContext、HttpRequest、HttpResponse,HttpContext包含了当前应用请求和响应的对象。
  • 当所有的核心对象都已经初始化,ASP.NET通过创建一个HttpApplication对象开始,如果应用包含一个Global.asax文件,ASP.NET会创建一个Global.asax类,该类是派生自HttpApplication,并使用该派生类来呈现应用。(第一次请求时,ASP.NET会创建HttpApplication的实例,为了性能考虑,该实例有可能会被复用)同时配置模块也会被创建。
  • HttpApplication类会执行内部的方法,例如验证等函数。

Global.asax文件

应用的生命周期中,应用会产生一些事件,开发者可以复写这些特殊的事件和方法,为了处理应用的事件,可以创建一个Global.asax的文件,位于应用的根目录。

如果创建了Global.asax,ASP.NET会将其编译成继承自HttpApplication的类,然后用它来代表应用。

每个HttpApplication同时只处理一个请求,好处就是不需要对非static得变量上锁。

ASP.NET自动绑定应用事件到Global.asax文件,通常事件名为Application_event,例如Application_BeginRequest.

注意Application_Start和Application_End方法比较特殊,这两个方法并不代表HttpApplication事件,ASP.NET在整个应用域生命周期中只调用一次

Application_event:Application_Error可以在应用的任何阶段调用,Application_EndRequest是唯一的事件每个请求都会调用

init:HttpApplication实例的所有模块被创建时调用

Dispose:应用实例被销毁之前会被调用,用来释放资源

Application_End:应用域被卸载前调用


Action Results

Web api的控制器的返回值可以是如下类型:

  • void
  • HttpResponseMessage
  • IHttpActionResult
  • 其它类型

根据返回值类型的不同,Web api使用不同的机制来返回HTTP响应信息。

返回值类型 Web api创建响应方式
void 返回204(No content)
HttpResponseMessage 直接转换成http响应的消息
IHttpActionResult 调用 ExecuteAsync 创建一个 HttpResponseMessage, 然后转换成一个Http 响应消息
其它类型 直接将对象序列化,并将序列化的结果写入body中,返回200(OK)

路由

ASP.NET 的 Web API,一个controller 被用来处理 HTTP 请求,Controller里的public方法被叫做action,当Web API接收到一个请求时,会将请求路由到对应的action。路由表位于App_Start\WebApiConfig.cs

默认情况下,路由表定义的模板为api/{controller}/{id},其中{}是占位符,当一个请求到达后,会按照路由表指定的模板进行匹配,如果没有任何一项能够匹配成功,那么就会报404错误。如果存在匹配的模板,Web api将会抽取出{controller}的内容,在其后加上Controller,匹配对应的controller,然后会根据http-method,选择对应的action,例如如果method为Get,会查找对应的GetXXX方法,Post会查找对应的PostXXX方法,然后将{i}作为参数传入方法。

如果不希望采用上述action的定位方式,可以通过annotation指定

  • [HttpGet]
  • [HttpPost]
  • [HttpPut]
  • [HttpDelete]
  • [HttpHead]
  • [HttpOptions]
  • [HttpPatch]

或者:

  • [AcceptVerbs("GET", "POST")]等

也可以在定义路由模板时指定Action占位符:api/{controller}/{action}/{id},[ActionName("XXX")]形式的Annotation,可以指定该方法可用来处理的action例如:

public class ProdctsController: ApiController {
    [HttpGet]
    [ActionName("Thumbnail")]
    public HttpResponseMessage GetThumbnailImage(int id);
}

路由为api/Products/Thumbnail/1时,会将请求定位到GetThumbnailImage
如果不希望一个公有的方法作为action,可以通过[NonAction]指定。

路由字典

当匹配到一个URI时,WebApi会创建一个字典,用来保存占位符中的每个值,key是占位符的名字,value是值,字典存储于IHttpRouteData对象中。如果一个占位符有默认值,且默认值为RouteParameter.Optional,当URI没有该占位符对应的值时,这个值不会保存在字典中。如果默认值定义的key-value不在路由模板中,那么该key-value会被存储在字典中。

选择一个Controller

控制器的选择是通过IHttpControllerSelector.SelectController这个方法。该方法接收一个HttpRequestMessage实例,返回一个HttpControllerDescriptor对象,默认的实现是由DefaultHttpControllerSelector类实现:

  1. 在路由字典中查找键controller对应的值
  2. 将值取出,并且在该字符串的末尾追加字符串"Controller"
  3. 通过第二步拼接出来的字符串寻找对应的控制器

如果找不到对应的控制器,将会返回一个错误给客户端

在第三步中,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口来获取所有web控制器列表,默认情况下,IhttpControllerTypeResolver只返回公有的非抽象的且结尾为Controller的并且继承自IHttpController的类。

选择Action

控制器选择完毕后,框架会调用IHttpActionSelector.SelectAction方法,这个方法需要HttpControllerContext作为参数,返回HttpActionDescriptor

默认通过ApiControllerActionSelector类实现,当进行action选择时,会考虑以下几个因素:

  • 请求的http-method
  • 在路由模板中是否存在{action}
  • action的参数

控制器的哪些方法可以被当做action:

  1. 必须是public访问权限
  2. 没有特殊的修饰(构造函数、events、overload等修饰符)
  3. 方法不能继承自ApiController

框架指选择以下的方法作为有效的Action:

  1. 由Annotation([AcceptVerbs]、[HttpGet]、[HttpPost]等)标记的方法。
  2. 由Get、Post、Put等开头的方法。
  3. 不满足1、2时,该方法只支持POST请求。

参数绑定规则:

  • 简单类型从URI获取
  • 复杂类型从request body获取

选择Action流程:

  1. 创建一个满足于该http-method的action列表
  2. 如果路由字典中有键action,从列表移除哪些不满足该值得action
  3. 匹配参数
    • 从URI中获取参数列表
    • 对每一个action,将action中的参数与参数列表匹配
    • 选择参数互相匹配的action
    • 如果有多个action满足,选择参数匹配最多的
  4. 忽略有[NonAction]标记的属性

如果没有找到对应的参数,并且该参数没有被标记optional,将报错。

属性Route

Web API引入的Route属性(Annotation),语法格式如下:

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

如果想启用Route属性,需要在WebApiConfig.cs文件中添加如下代码:

config.MapHttpAttributeRoutes();

Route属性可以与路由模板一同使用。

Route属性可以与http-method结合使用:

public class OrdersController: ApiController {
    [Route("api/v2/{say}")]
    [HttpGet]//或者[AcceptVerbs("Get")]
    public HttpResponseMessage say(int say) { ... }
}

只有通过get方式才能够访问。Route属性的参数也可以指定多个。

Route属性可以指定前缀:

[RoutePrefix("api/books")]
public class BooksController: ApiController {
    [Route("")] //api/books
    public HttpResponseMessage index() {....}

    [Route("{id:int}")]//api/books/id
    public HttpResponseMessage indexs(int id) { ... }

    [Route("~/v1/book")]// ~取消前缀,/v1/book
    public HttpResponseMessage v1Books() {....}
}

Route类型限制

[Route("users/{id:int}")]
public User GetUserById(int id) { ... }

Route 可以自定义限制类型:

自定义路由参数限制类型可以继承自IHttpRouteConstraint,并且覆写Match方法。然后在WebApiConfig.cs中的Register方法中注册该限制:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var constraintResolver = new DefaultInlineConstraintResolver();
        constraintResolver.ConstraintMap.Add("nonzero", typeof(NonZeroConstraint));

        config.MapHttpAttributeRoutes(constraintResolver);
    }
}

在指定的Route中使用该规则:

[Route("{id:nonzero}")]
...

当需要Route指定默认值时有两种方式:

public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int?}")]
    public IEnumerable<Book> GetBooksByLocale(int lcid = 1033) { ... }
}
public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int=1033}")]
    public IEnumerable<Book> GetBooksByLocale(int lcid) { ... }
}

两种方式结果一样,但是流程和效率不一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容