ASP.NET Web Api第一條請求和路由

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):

  1. 在路由字典中查找鍵controller對應(yīng)的值
  2. 將值取出,并且在該字符串的末尾追加字符串"Controller"
  3. 通過第二步拼接出來的字符串尋找對應(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:

  1. 必須是public訪問權(quán)限
  2. 沒有特殊的修飾(構(gòu)造函數(shù)、events、overload等修飾符)
  3. 方法不能繼承自ApiController

框架指選擇以下的方法作為有效的Action:

  1. 由Annotation([AcceptVerbs]、[HttpGet]、[HttpPost]等)標記的方法。
  2. 由Get、Post、Put等開頭的方法。
  3. 不滿足1、2時,該方法只支持POST請求。

參數(shù)綁定規(guī)則:

  • 簡單類型從URI獲取
  • 復雜類型從request body獲取

選擇Action流程:

  1. 創(chuàng)建一個滿足于該http-method的action列表
  2. 如果路由字典中有鍵action,從列表移除哪些不滿足該值得action
  3. 匹配參數(shù)
    • 從URI中獲取參數(shù)列表
    • 對每一個action,將action中的參數(shù)與參數(shù)列表匹配
    • 選擇參數(shù)互相匹配的action
    • 如果有多個action滿足,選擇參數(shù)匹配最多的
  4. 忽略有[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é)果一樣,但是流程和效率不一樣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容