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
类实现:
- 在路由字典中查找键controller对应的值
- 将值取出,并且在该字符串的末尾追加字符串"Controller"
- 通过第二步拼接出来的字符串寻找对应的控制器
如果找不到对应的控制器,将会返回一个错误给客户端
在第三步中,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口来获取所有web控制器列表,默认情况下,IhttpControllerTypeResolver只返回公有的非抽象的且结尾为Controller的并且继承自IHttpController的类。
选择Action
控制器选择完毕后,框架会调用IHttpActionSelector.SelectAction
方法,这个方法需要HttpControllerContext
作为参数,返回HttpActionDescriptor
。
默认通过ApiControllerActionSelector
类实现,当进行action选择时,会考虑以下几个因素:
- 请求的
http-method
- 在路由模板中是否存在
{action}
- action的参数
控制器的哪些方法可以被当做action:
- 必须是public访问权限
- 没有特殊的修饰(构造函数、events、overload等修饰符)
- 方法不能继承自ApiController
框架指选择以下的方法作为有效的Action:
- 由Annotation([AcceptVerbs]、[HttpGet]、[HttpPost]等)标记的方法。
- 由Get、Post、Put等开头的方法。
- 不满足1、2时,该方法只支持POST请求。
参数绑定规则:
- 简单类型从URI获取
- 复杂类型从request body获取
选择Action流程:
- 创建一个满足于该
http-method
的action列表 - 如果路由字典中有键action,从列表移除哪些不满足该值得action
- 匹配参数
- 从URI中获取参数列表
- 对每一个action,将action中的参数与参数列表匹配
- 选择参数互相匹配的action
- 如果有多个action满足,选择参数匹配最多的
- 忽略有[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) { ... }
}
两种方式结果一样,但是流程和效率不一样。