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的實例,為了性能考慮,該實例有可能會被復(fù)用)同時配置模塊也會被創(chuàng)建色鸳。
- HttpApplication類會執(zhí)行內(nèi)部的方法社痛,例如驗證等函數(shù)。
Global.asax文件
應(yīng)用的生命周期中命雀,應(yīng)用會產(chǎn)生一些事件蒜哀,開發(fā)者可以復(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獲取
- 復(fù)雜類型從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é)果一樣赎离,但是流程和效率不一樣。