ASP.NET應(yīng)用程序的生命周期
- ASP.NET程序的生命周期開始于用戶通過瀏覽器向Web服務(wù)器發(fā)送一個請求,ASP.NET是一個ISAPI Web 服務(wù)器的一個擴展,當服務(wù)器接收到一個請求,服務(wù)器會檢查被請求文件的擴展名,通過這個擴展名決定該使用哪個ISAPI來處理該請求,然后將該請求發(fā)送到相關(guān)的ISAPI擴展。
- 當ASP.NET程序收到第一個請求時,ApplicationManager會創(chuàng)建一個應(yīng)用域,應(yīng)用域?qū)⒉煌腁SP.NET和其全局變量隔離開來,在應(yīng)用域內(nèi),一個HostingEnvironment的實例被創(chuàng)建,該對象提供了應(yīng)用的一些信息,例如文件夾的名字等。ASP.NET也會變異頂級的項目,包括位于App_Code文件夾下的代碼。
- ASP.NET會創(chuàng)建并初始化核心的對象:HttpContext、HttpRequest、HttpResponse,HttpContext包含了當前應(yīng)用請求和響應(yīng)的對象。
- 當所有的核心對象都已經(jīng)初始化,ASP.NET通過創(chuàng)建一個HttpApplication對象開始,如果應(yīng)用包含一個Global.asax文件,ASP.NET會創(chuàng)建一個Global.asax類,該類是派生自HttpApplication,并使用該派生類來呈現(xiàn)應(yīng)用。(第一次請求時,ASP.NET會創(chuàng)建HttpApplication的實例,為了性能考慮,該實例有可能會被復用)同時配置模塊也會被創(chuàng)建。
- HttpApplication類會執(zhí)行內(nèi)部的方法,例如驗證等函數(shù)。
Global.asax文件
應(yīng)用的生命周期中,應(yīng)用會產(chǎn)生一些事件,開發(fā)者可以復寫這些特殊的事件和方法,為了處理應(yīng)用的事件,可以創(chuàng)建一個Global.asax的文件,位于應(yīng)用的根目錄。
如果創(chuàng)建了Global.asax,ASP.NET會將其編譯成繼承自HttpApplication的類,然后用它來代表應(yīng)用。
每個HttpApplication同時只處理一個請求,好處就是不需要對非static得變量上鎖。
ASP.NET自動綁定應(yīng)用事件到Global.asax文件,通常事件名為Application_event,例如Application_BeginRequest.
注意Application_Start和Application_End方法比較特殊,這兩個方法并不代表HttpApplication事件,ASP.NET在整個應(yīng)用域生命周期中只調(diào)用一次
Application_event:Application_Error可以在應(yīng)用的任何階段調(diào)用,Application_EndRequest是唯一的事件每個請求都會調(diào)用
init:HttpApplication實例的所有模塊被創(chuàng)建時調(diào)用
Dispose:應(yīng)用實例被銷毀之前會被調(diào)用,用來釋放資源
Application_End:應(yīng)用域被卸載前調(diào)用
Action Results
Web api的控制器的返回值可以是如下類型:
- void
- HttpResponseMessage
- IHttpActionResult
- 其它類型
根據(jù)返回值類型的不同,Web api使用不同的機制來返回HTTP響應(yīng)信息。
| 返回值類型 | Web api創(chuàng)建響應(yīng)方式 |
|---|---|
| void | 返回204(No content) |
| HttpResponseMessage | 直接轉(zhuǎn)換成http響應(yīng)的消息 |
| IHttpActionResult | 調(diào)用 ExecuteAsync 創(chuàng)建一個 HttpResponseMessage, 然后轉(zhuǎn)換成一個Http 響應(yīng)消息 |
| 其它類型 | 直接將對象序列化,并將序列化的結(jié)果寫入body中,返回200(OK) |
路由
ASP.NET 的 Web API,一個controller 被用來處理 HTTP 請求,Controller里的public方法被叫做action,當Web API接收到一個請求時,會將請求路由到對應(yīng)的action。路由表位于App_Start\WebApiConfig.cs。
默認情況下,路由表定義的模板為api/{controller}/{id},其中{}是占位符,當一個請求到達后,會按照路由表指定的模板進行匹配,如果沒有任何一項能夠匹配成功,那么就會報404錯誤。如果存在匹配的模板,Web api將會抽取出{controller}的內(nèi)容,在其后加上Controller,匹配對應(yīng)的controller,然后會根據(jù)http-method,選擇對應(yīng)的action,例如如果method為Get,會查找對應(yīng)的GetXXX方法,Post會查找對應(yīng)的PostXXX方法,然后將{i}作為參數(shù)傳入方法。
如果不希望采用上述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會創(chuàng)建一個字典,用來保存占位符中的每個值,key是占位符的名字,value是值,字典存儲于IHttpRouteData對象中。如果一個占位符有默認值,且默認值為RouteParameter.Optional,當URI沒有該占位符對應(yīng)的值時,這個值不會保存在字典中。如果默認值定義的key-value不在路由模板中,那么該key-value會被存儲在字典中。
選擇一個Controller
控制器的選擇是通過IHttpControllerSelector.SelectController這個方法。該方法接收一個HttpRequestMessage實例,返回一個HttpControllerDescriptor對象,默認的實現(xiàn)是由DefaultHttpControllerSelector類實現(xiàn):
- 在路由字典中查找鍵controller對應(yīng)的值
- 將值取出,并且在該字符串的末尾追加字符串"Controller"
- 通過第二步拼接出來的字符串尋找對應(yīng)的控制器
如果找不到對應(yīng)的控制器,將會返回一個錯誤給客戶端
在第三步中,DefaultHttpControllerSelector使用IHttpControllerTypeResolver接口來獲取所有web控制器列表,默認情況下,IhttpControllerTypeResolver只返回公有的非抽象的且結(jié)尾為Controller的并且繼承自IHttpController的類。
選擇Action
控制器選擇完畢后,框架會調(diào)用IHttpActionSelector.SelectAction方法,這個方法需要HttpControllerContext作為參數(shù),返回HttpActionDescriptor。
默認通過ApiControllerActionSelector類實現(xiàn),當進行action選擇時,會考慮以下幾個因素:
- 請求的
http-method - 在路由模板中是否存在
{action} - action的參數(shù)
控制器的哪些方法可以被當做action:
- 必須是public訪問權(quán)限
- 沒有特殊的修飾(構(gòu)造函數(shù)、events、overload等修飾符)
- 方法不能繼承自ApiController
框架指選擇以下的方法作為有效的Action:
- 由Annotation([AcceptVerbs]、[HttpGet]、[HttpPost]等)標記的方法。
- 由Get、Post、Put等開頭的方法。
- 不滿足1、2時,該方法只支持POST請求。
參數(shù)綁定規(guī)則:
- 簡單類型從URI獲取
- 復雜類型從request body獲取
選擇Action流程:
- 創(chuàng)建一個滿足于該
http-method的action列表 - 如果路由字典中有鍵action,從列表移除哪些不滿足該值得action
- 匹配參數(shù)
- 從URI中獲取參數(shù)列表
- 對每一個action,將action中的參數(shù)與參數(shù)列表匹配
- 選擇參數(shù)互相匹配的action
- 如果有多個action滿足,選擇參數(shù)匹配最多的
- 忽略有[NonAction]標記的屬性
如果沒有找到對應(yīng)的參數(shù),并且該參數(shù)沒有被標記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結(jié)合使用:
public class OrdersController: ApiController {
[Route("api/v2/{say}")]
[HttpGet]//或者[AcceptVerbs("Get")]
public HttpResponseMessage say(int say) { ... }
}
只有通過get方式才能夠訪問。Route屬性的參數(shù)也可以指定多個。
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 可以自定義限制類型:
自定義路由參數(shù)限制類型可以繼承自
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中使用該規(guī)則:
[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) { ... }
}
兩種方式結(jié)果一樣,但是流程和效率不一樣。