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 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 GetBooksByLocale(int lcid = 1033) { ... }
}
public class BooksController : ApiController
{
    [Route("api/books/locale/{lcid:int=1033}")]
    public IEnumerable GetBooksByLocale(int lcid) { ... }
}

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

你可能感兴趣的:(ASP.NET Web Api第一条请求和路由)