路由機制


VC框架中路由具有重要作用,本文主要介紹路由的一些知識。目錄如下:

1、引言

2、什么是路由

3、特性路由

4、傳統路由

5、MVC區域

6、路由調試

7、路由的其它一些信息

8、選擇特性路由還是傳統路由

9、asp.net處理http請求的大致過程

 

1、引言

MVC的理解:

View是界面,Model是功能模型,Controller是View和Model的橋接,將View的輸入傳遞到Model,並將Model的結果反饋到View。

例如:總統在舞台上演講,總統口渴了需要水;秘書負責傳喚幕后人員送上水來;后台人員負責送水。總統以及舞台是view,秘書是controller,后台人員是model。總統的要求都是由model實際來完成的,controller只是個傳話的。

 

查看mvc實例:創建一個mvc項目,首先查看它的全局文件:

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
    }

對start函數中的其它幾項先不做說明,此處查看RegisterRoutes靜態方法:

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );
        }

直接運行網站http://localhost:3306/,得到頁面,切圖如下:

類似URL也可以得到該頁面:

http://localhost:3306/home,http://localhost:3306/home/index/,http://localhost:3306/home/index/1

但是http://localhost:3306/home/index/1/1打開是錯誤頁面。

我們輸入的URL為何可以轉到控制器,或者說上文總統想要喝水的指令是如何傳達到秘書那里的,其中牽涉到MVC路由。

 

2、什么是路由

上述通過url打開頁面,可以說是瀏覽器http請求,通過路由機制到具體的頁面。那么,什么是路由呢?路由相當於是一個中轉,是一個配置。也許單純說路由的定義,顯得很抽象,那直接說路由能干什么:

ASP.net MVC的路由主要用途有兩種:

1、 匹配傳入的請求,該請求不匹配服務器文件系統中的文件,而是把請求映射到控制器操作;

2、 構造傳出的URL,用來響應控制器操作;

表面來看,路由和URL重寫很相似,此處簡單說說二者區別:

URL重寫關注的是將一個URL映射到另一個URL,路由關注的則是如何將URL映射到資源;

URL重寫只能用於傳入的請求URL,而不能幫助生成原始的URL,路由卻可以使用它在匹配傳入URL時用到的映射規則來幫助生成URL。

 

3、MVC5特性路由

特性路由時mvc5新增的一個特性,本文先從特性路由講起。啟用特性路由,首先在RegisterRoutes方法中刪除其它路由,只通過調用MapMvcAttributeRoutes啟用特性路由:

public static void RegisterRoutes(RouteCollection routes)
        {
            routes.MapMvcAttributeRoutes();
        }

 新增一個控制器類Book:

public class BookController : Controller
    {
        [Route("Index")]
        public ActionResult Index()
        {
            return View();
        }
    }

訪問URL:http://localhost:56978/index可打開相應的index頁面。

 

Book類中新增一方法:

[Route("")]
        [Route("book")]
        [Route("book/about")]
        public ActionResult About()
        {
            return View();
        }

那么,URL都能訪問該頁面:/,/book/,/book/about。

上述講到的是靜態路由,但是並非所有的URL都是靜態的,通過添加路由參數可以解決此問題:

[Route("book/info/{id}")]
        public ActionResult Info(int id)
        {
            ViewData["id"] = id;
            return View();
        }

加入帶參數路由,URL:http://localhost:56978/book/info/2可以進行訪問。

問題來了,如果該控制器中有多個頁面,訪問頁面都需要加上/book,難道所有的方法都需要一一添加?肯定不是的,控制器類路由可以解決此問題。

[Route("phone/{action}")]
    public class PhoneController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            return View();
        }
    }

如果訪問phone下面的index,或者是about頁面,url:http://localhost:56978/phone/about或index都可以進行訪問。

但是,有時控制器上的某些操作具有與其他操作稍微不同的路由,那么我們可以直接把最通用的路由放到控制器上面,然后在具有不同路由路由模式的操作上重寫路由。

[Route("phone/{action}")]
    public class PhoneController : Controller
    {
        [Route ("phone2/index")]
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            return View();
        }
    }

URL:http://localhost:56978/phone2/index可以進行訪問demo phone page index頁面。但是URL:http://localhost:56978/phone/index頁面卻不能進行訪問,也證實了在操作方法級別指定路由特性時,會覆蓋控制器級別指定的任何路由特性

 前面的類仍然帶有重復性,通過使用RoutePrefix,可以僅在一個地方指定路由以phone/開頭。

    [RoutePrefix("phone")]
    [Route("{action}")]
    public class PhoneController : Controller
    {
        [Route("")]
        public ActionResult Index()
        {
            return View();
        }

        [Route("")]
        public ActionResult About()
        {
            return View();
        }
    }

URL:http://localhost:56978/phone/,打開報錯:

 如果想讓phoneController支持”/”,那么使用~/作為路由模板的開頭,路由前綴就會忽略。在phone的操作方法中再加入[Route("~/")]:

[RoutePrefix("phone")]
    [Route("{action}")]
    public class PhoneController : Controller
    {
        [Route("")]
        public ActionResult Index()
        {
            return View();
        }

        [Route("~/")]
        public ActionResult About()
        {
            return View();
        }
    }

URL:http://localhost:56978/打開demo phone page about頁面。

路由約束

路由約束是一種條件,只有滿足該條件,路由才能匹配。

[Route("book/info/{id}")]
        public ActionResult Info(int id)
        {
            ViewData["id"] = id;
            return View();
        }

        [Route("book/list/{id}")]
        public ActionResult List()
        {
            return View();
        }

上述info頁面,URL:http://localhost:56978/book/info/2可以打開,而url:http://localhost:56978/book/info/name,報錯:

 

上述list頁面,URL:http://localhost:56978/book/list/name,http://localhost:56978/book/list/2都可以打開。

將路由參數定義為{id:int},如此放到路由模板中的約束叫做內聯約束。如此,將list參數加入路由內聯約束,也可以達到此效果:

[Route("book/list/{id:int}")]
        public ActionResult List()
        {
            return View();
        }

 常用的路由約束:

路由的默認值:

//[Route("money/{action}")]
    [Route("money/{action=index}")]
    public class MoneyController : Controller
    {
        // GET: Money
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult About()
        {
            return View();
        }
    }

那么url:http://localhost:56978/money/默認和http://localhost:56978/money/index,同時不影響http://localhost:56978/money/about打開about頁面。

將參數當作可選參數:

[Route ("money/list/{id?}")]
        public ActionResult List()
        {
            return View();
        }

Url:http://localhost:56978/money/list和http://localhost:56978/money/list/4同樣可以打開list頁面。

但是:

[Route ("money/list/{id?}")]
        public ActionResult List(int id)
        {
            return View();
        }

URL:http://localhost:56978/money/list/,不能打開頁面。側面反映了可選參數也是基於操作方法來做具體調整的。

 

4、傳統路由

此處先禁用特性路由,編寫一個最傳統的路由規則,傳統路由都需要在RegisterRoutes下面進行定義:

public static void RegisterRoutes(RouteCollection routes)
        {
            //routes.MapMvcAttributeRoutes();//book,phone,money

            routes.MapRoute("simple", "{controller}/{action}/{id}");
        }

編寫控制器:

public class THomeController : Controller
    {
        // GET: THome
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Detail(int id)
        {
            return View();
        }
    }

url:http://localhost:56978/thome/index/3打開index頁面。

傳統路由URL在段中也允許包含字面值,例如:可能會把mvc集中到某一個現有站點中,並且所有mvc請求都必須以site開頭:

routes.MapRoute("simple", "site/{controller}/{action}/{id}");

還是THome控制器,打開url:http://localhost:56978/site/thome/index/3,可以打開index頁面。

 傳統路由具有更加靈活的路由語法規則:在路徑段中允許字面值和路由參數混合在一起。僅有的限制是不允許有兩個連續的路由參數。只需要記住,除非路由提供了controller和action參數,否則MVC不知道為URL允許那些代碼。

具體實例看下面路由規則:

routes.MapRoute("simple", "{lanuage}-{city}/{controller}/{action}");//true
            routes.MapRoute("simple", "{controller}.{action}.{id}");//true

            routes.MapRoute("simple", "{controller}{action}/{id}");//false

 

傳統路由默認值

定義的路由如下:

routes.MapRoute("simple", "{controller}/{action}/{id}", new { id = UrlParameter.Optional, action = "index" });

上述路由規則,id是可選參數,action默認是index,同樣的,也可以默認控制器如controller="thome"。

路由約束
路由如下:

routes.MapRoute("simple3", "{year}/{month}/{day}", new { controller = "thome", action = "index" }, new { year = @"\d{4}", month = @"\d{2}", day = @"\d{2}" });

URL:http://localhost:56978/2018/05/30,打開index頁面。

上述約束采用正則進行實現。值得注意的是:特性路由的正則表達式的匹配行為與傳統路由相反。傳統路由總是進行精確匹配,而特性路由的regex內聯約束支持部分匹配。例如:傳統路由約束year=@”\d{4}”相當於特性路由內聯約束{year:regex(^\d{4}$)}。在特性路由中,如果需要進行精確匹配,必須顯示包含^和$字符。傳統路由總是會替我們添加這些字符,不編寫自定義約束,是無法進行部分匹配的。我們通常進行的是精確字符串匹配,所有傳統路由語法意味着我們不會忘記這些細節。

路由命名:

Asp.net的路由機制不要求路由具有名稱,而且絕大多數情況下沒有名稱的路由也能滿足我們的應用,例如:

routes.MapRoute(
                name: "Test",
                url: "code/p/{action}/{id}",
                defaults: new { controller = "Section", action = "Index", id = "" }
                );

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = "" }
                );

添加tstudents的index頁面:

@{
    ViewBag.Title = "Index";
}

<h2>Demo TStudent page Index</h2>

<div>
    <form method="post" id="testCreateUrl" name="testCreateUrl">
        @Html.RouteLink("to default", new { controller = "Home", action = "Index", id = 123 })

        @Html.RouteLink("to Test", new { controller = "section", action = "Index", id = 123 })</form>
</div>

則生成相應的url:http://localhost:56978/Home/Index/123,http://localhost:56978/code/p/Index/123。

上述路由非常簡單,但是有時候我們會碰到一些特殊情況:

例如web窗體應用程序路由,希望/aspx/SomePage.aspx,能夠處理/static/url:

routes.MapPageRoute("new", "static/url", "~/aspx/SomePage.aspx");

同時在RegisterRoutes方法中,上述路由不能放入路由列表的末尾,否則就不能匹配傳入的請求,因此放在前面。從而導致上述生成的url:

http://localhost:56978/static/url?controller=Home&action=Index&id=123和http://localhost:56978/static/url?controller=section&action=Index&id=123,

很明顯不符合要求,於是使用路由命名:

@Html.RouteLink(
             linkText: "route:test",
             routeName: "test",
             routeValues: new { controller = "section", action = "Index", id = 123 })
        @Html.RouteLink(
             linkText: "route:default",
             routeName: "default",
             routeValues: new { controller = "Home", action = "Index", id = 123 })

結果達到預期。

 

 5、MVC區域

創建一個區域User,新增UsersController中的index頁面,HomeController中的index頁面。

區域路由中默認增加路由規則:

public override void RegisterArea(AreaRegistrationContext context) 
        {

            context.MapRoute(
                "User_default",
                "User/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }

 

http://localhost:56978/home/index進行訪問:

 

URL:http://localhost:56978/user/home/index/,打開區域內的homecontroller的index頁面。

 上述出現路由沖突,

當使用add area對話框添加區域時,框架會相應的在該區域的名稱空間中為新區域注冊一個路由,這樣就保證只有新區域中的控制器才能匹配新路由。

名稱空間可以縮小匹配路由時控制器的候選集。如果路由指定了匹配的名稱空間,那么只有在這個名稱空間中的控制器才有可能與該路由匹配。相反,如果路由沒有指定名稱空間,那么程序中所有的控制器都有可能與該路由匹配,而且很容易導致二義性,即兩個同名控制器同時匹配一個路由。防止出現該二義性的方法是在整個項目中使用唯一的控制器名稱。但是如果有時候想使用相同的控制器名稱時,可以在特定的路由下指定控制器類的名稱空間。如:

 在區域路由中添加:

public override void RegisterArea(AreaRegistrationContext context) 
        {
            context.MapRoute(
                "User_default",
                "User/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional },
                new [] { "Demo.Areas.User.Controllers" }
            );
        }

在外部路由中添加:

routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = "" },
                namespaces: new[] { "Demo.Controllers" }
                );

打開URL:http://localhost:56978/Home/Index,打開demo home page index頁面。URL:http://localhost:56978/user/home/index/,打開UserArea home page Index頁面。

 

特性路由中使用區域,需要使用RouteArea特性。在特性路由中,不需要指定名稱空間,因為mvc會完成確定名稱空間的工作(特性放到了控制器上,而控制器指定它自己的名稱空間。)

[RouteArea ("User")]
    [Route("users/{action}")]
    public class UserController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

    }

 

URL:http://localhost:56978/user/dept/index可以訪問。按道理來說,應該是可以的,但是區域內使用特性路由一直行不通。???

如果想更改不同的路由前綴:

 

Catchall
定義可變長度的自定義片段變量,通過在片段變量前加*號前綴來定義匹配任意數量片段的路由:

routes.MapRoute("catchallroute", "{controller}/{action}/{id}/{*catchall}",
                new { controller = "Home", action = "Index", id = UrlParameter.Optional },
          new[] { "Demo.Areas.User.Controllers" }
);

控制器類:

public class THomeController : Controller
    {
        // GET: THome
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Detail(int id)
        {
            return View();
        }
    }

URL:http://localhost:56978/thome/detail/12/23/34,可打開thome page detail頁面。

 

StopRoutingHandler IgnoreRoute

默認情況下,路由機制會忽略那些映射到磁盤物理文件的請求,但是有一些應用場合,一些不能映射到磁盤文件的請求也不需要路由來處理,例如asp.net的web資源處理程序WebResource.axd的請求,是由一個http處理程序來處理的,而它們並沒有對應對磁盤上的文件。StopRoutingHandler可以確保路由忽略這種請求,如:

routes.Add(new Route( "{resource}.axd/{*pathInfo}", new StopRoutingHandler()));

如果傳入了url為/WebResource.axd的請求,它會與第一個路由相匹配,路由返回一個StopRoutingHandler的對象,所以路由會繼續把該請求傳遞給標准的asp.net處理程序,最終將回到用於處理.axd擴展的標准http處理程序。

還有一種更為簡單的路由機制忽略指定路由,即IgnoreRoute:

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

看到此處是不是很熟悉,MVC默認配置。

 

6 、路由調試:

路由匹配過程中如果出現問題是令人沮喪的一件事,因為路由時asp.net內部路由處理邏輯解析,不能設置斷點進行調試。而且路由時按先后順序匹配的,且第一個匹配成功的路由生效,很有可能發生錯誤不在路由的定義上,而是該路由沒有在路由列表的正確位置上。於是使用RouteDebugger:

使用NuGet進行安裝:

 

在web.Config進行設置:

<add key="RouteDebugger:Enabled" value="false" /></appSettings>

將值改為true,即開啟路由調試。

 

 7、路由如何生成URL

前面已經介紹路由時如何匹配傳入的請求URL,而路由的另一個職責則是構造與特定路由對應的URL。生成URL的方式有多種,但是最后都是調用RouteCollection. GetVirtualPath的重載方法,有兩個版本:

public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values);
public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);

接收當前的RequestContext,路由集合通過RouteBase. GetVirtualPath方法遍歷每個路由並詢問,可以根據給定參數生成url嗎?能則返回VirtualPathData的實例,否則返回空值,轉移到下一個路由。第二個重載方法則直接找到指定的名稱的路由,匹配則返回實例,不匹配,則返回空值,並且不再匹配其他路由。
VirtualPathData:表示有關路由和虛擬路徑的信息,該路由和虛擬路徑是在使用 ASP.NET 路由框架生成 URL 時產生的。

 

1 頁面調用Html.ActionLink或Url.Action方法,此方法再調用RouteCollection. GetVirtualPath方法,並傳入一個RequestContext對象、一個包含值的字典,以及用來生成url的路由名稱(可選).

2 路由機制查看要求的路由參數(即提供路由參數的默認值),並確保提供的路由值字典為每一個要去的參數提供一個值。否則,url生成過程會立即停止,並返回空值。

3 一些路由可能包含沒有對應路由參數的默認值。

4 路由系統應用路由的約束。

5 路由匹配成功,通過查看每一個路由參數,嘗試利用字典中的對應值填充相應參數,進而生成url。

 

路由綁定到操作

當asp.net處理請求時,路由管道主要:

1 UrlRoutingModule嘗試使用在RouteTable中注冊的路由匹配當前的請求;

2 如果RouteTable中有一個路由匹配成功,路由模塊會從成功匹配的路由中獲取IRouteHandler接口對象;

3 路由模塊調用IRouteHandler接口的GetHandler方法,並返回用來處理請求的IHttpHandler對象;

4 調用http處理程序的ProcessRequest方法,然后把要處理的請求傳遞給它。

5 在asp.net mvc中,IRouteHandler是MvcRouteHandler類的一個實例,MvcRouteHandler轉而返回一個實現了IHttpHandler接口的MvcHandler對象。返回的MvcHandler對象主要用來實例化控制器,並調用該實例化的控制器上的操作方法。

 

路由數據:調用GetRouteData方法會返回RouteData的一個實例,RouteData則包含了關於匹配請求的路由信息。

 

路由規則:{controller}/{action}/{id},當一個請求例如/home/index/123傳入時,該路由會嘗試匹配傳入的請求。如果匹配成功,它就創建一個字典,其中包含了從URL中解析出的信息,更確切的說,路由還會向values字典中為URL中的每個路由參數添加一個鍵。對於上例來說,至少會有cotroller鍵的值為home,action鍵的值為index,id鍵的值為123.對於特性路由來說,MVC使用DataTokens字典來存儲更精確的信息。在整個MVC中都有用到的RequestContext的RouteData屬性保存着外界路由值。

 8、選擇特性路由還是傳統路由

特性路由和傳統路由結合使用時,路由系統按順序檢查每個路由,並選擇第一個匹配的路由。在實際使用過程中,建議將routes.MapMvcAttributeRoutes();放到首位,特性路由通常更加具體,而傳統路由更加寬泛。

選擇傳統路由:
想要集中配置所以路由;
使用自定義約束對象;
存在現有可工作的應用程序,而又不想修改應用程序。

選擇特性路由:
想把路由與操作代碼保存在一起;
創建新應用程序,或者對現有應用程序進行巨大修改;

傳統路由的集中配置意味着可以在一個地方理解請求如何映射到操作,傳統路由對自定義約束對象也比特性路由來說更加容易。優先選擇特性路由的原因是特性路由很好地把關於控制器的所有內容放到了一起,包括控制器使用的URL和運行的操作。

 

9、asp.net處理http請求的大致過程

此處稍微描述下asp.net處理http請求的過程,之所以寫這個,是因為前面大段篇幅介紹了MVC的路由,是個中間處理過程,那前面的過程是個什么樣的,過來一個http請求,怎么處理呢,怎么就到路由了。

如今基於asp.net開發web程序,基本上都是發布部署到安裝了IIS的windows服務器上。如此,客戶端瀏覽器向服務器發出一個http請求,當IIS檢測到某個HTTP Request后,先根據擴展名判斷請求的是否是靜態資源(比如.html,.img,.txt,.xml等),如果是則直接將文件內容以HTTP Response的形式返回。如果是動態資源(比如.aspx,asp,php等等),則通過擴展名從IIS的腳本影射(Script Map)找到相應的ISAPI Dll。接着它又通過Http Pipeline的管道,傳到工作進程(IIS5.x為aspnet_wp.exe,IIS6.x和IIS7.x為w3wp.exe)后,工作進程實例中通過ISAPIRuntime(主要作用是調用一些非托管代碼生成HttpWorkerRequest對象,HttpWorkerRequest對象包含當前請求的所有信息,然后傳遞給HttpRuntime)傳遞HttpWorkerRequest對象給HttpRuntime,並調用HttpRuntime的ProcessRequest方法,HttpRuntime為管道模型的入口此時正式進入管道模型。

HttpRuntime根據HttpWorkerRequest對象生成HttpContext, 再調用HttpApplicationFactory的GetApplicationInstance方法生成HttpApplication,HttpApplication對象包含多個HttpModule對象,當HttpApplication,調用HttpHandlerFactory的GetHandler方法生成具體的HttpHandler對象,將控制權交給HttpHandler,來處理http請求並返回http響應,再經過HttpApplication對象的一系列事件最終返回到客戶端。

注:當一個HTTP請求到達HttpModule時,整個ASP.NET Framework系統還並沒有對這個HTTP請求做任何處理,也就是說此時對於HTTP請求來講,HttpModule是一個HTTP請求的“必經之路”,所以可以在這個HTTP請求傳遞到真正的請求處理中心(HttpHandler)之前附加一些需要的信息在這個HTTP請求信息之上,或者針對截獲的這個HTTP請求信息作一些額外的工作,或者在某些情況下干脆終止滿足一些條件的HTTP請求,從而可以起到一個Filter過濾器的作用。

httpContext,稱為上下文,msdn:

附上兩張圖:

HttpApplication

HttpApplication是整個ASP.NET基礎架構的核心,它負責處理分發給它的HTTP請求。由於一個HttpApplication對象在某個時刻只能處理一個請求,只有完成對某個請求的處理后,HttpApplication才能用於后續的請求的處理。所以,ASP.NET采用對象池的機制來創建或者獲取HttpApplication對象。具體來講,當第一個請求抵達的時候,ASP.NET會一次創建多個HttpApplication對象,並將其置於池中,選擇其中一個對象來處理該請求。當處理完畢,HttpApplication不會被回收,而是釋放到池中。對於后續的請求,空閑的HttpApplication對象會從池中取出,如果池中所有的HttpApplication對象都處於繁忙的狀態,ASP.NET會創建新的HttpApplication對象。

HttpApplication處理請求的整個生命周期是一個相對復雜的過程,在該過程的不同階段會觸發相應的事件。我們可以注冊相應的事件,將我們的處理邏輯注入到HttpApplication處理請求的某個階段。

 

HttpModule

ASP.NET為創建各種.NET Web應用提供了強大的平台,它擁有一個具有高度可擴展性的引擎,並且能夠處理對於不同資源類型的請求。那么,是什么成就了ASP.NET的高可擴展性呢? HttpModule功不可沒。

從功能上講,HttpModule之於ASP.NET,就好比ISAPI Filter之於IIS一樣。IIS將接收到的請求分發給相應的ISAPI Extension之前,注冊的ISAPI Filter會先截獲該請求。ISAPI Filter可以獲取甚至修改請求的內容,完成一些額外的功能。與之相似地,當請求轉入ASP.NET管道后,最終負責處理該請求的是與請求資源類型相匹配的HttpHandler對象,但是在Handler正式工作之前,ASP.NET會先加載並初始化所有配置的HttpModule對象。HttpModule在初始化的過程中,會將一些功能注冊到HttpApplication相應的事件中,那么在HttpApplication整個請求處理生命周期中的某個階段,相應的事件會被觸發,通過HttpModule注冊的事件處理程序也得以執行。

 

HttpHandler

如果說HttpModule相當於IIS的ISAPI Filter的話,我們可以說HttpHandler則相當於IIS的ISAPI Extension,HttpHandler在ASP.NET中扮演請求的最終處理者的角色。對於不同資源類型的請求,ASP.NET會加載不同的Handler來處理,也就是說.aspx page與.asmx web service對應的Handler是不同的。

所有的HttpHandler都實現了接口IHttpHandler。下面是IHttpHandler的定義,方法ProcessRequest提供了處理請求的實現。

public interface IHttpHandler
    {
        bool IsReusable { get; }
        void ProcessRequest(HttpContext context);
    }

 

10、MVC路由核心

http請求被IIS和Asp.net處理流程

1請求被UrlRoutingModule部件攔截

2封裝請求上下文HttpContext,成為HttpContextWrapper對象。

3根據當前的HttpContext,從Routes集合中得到與當前請求URL相符合的RouteData對象。

4將RouteData與HttpContext請求封裝成一個RequestContext對象。

5根據RequestContext對象,從RouteData的RouteHandler中獲取IHttpHandler(MVC里面會有一個IHttpHandler的實現類MvcHandler)。

6執行IHttpHandler(MvcHandler),然后就是通過反射激活具體的controller,執行具體的action。

下面是路由注冊的源碼:

上述請求在asp.net管道事件中注冊的事件:

1、請求被UrlRoutingModule部件攔截————通過注冊HttpApplication對象的PostResolveRequestCache事件來實現攔截

2、封裝請求上下文HttpContext,成為HttpContextWrapper對象。————將UrlRoutingModule的Init()方法轉到定義,可以看到這么一句: HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);

3、根據當前的HttpContext,從Routes集合中得到與當前請求URL相符合的RouteData對象。————將UrlRoutingModule的Init()方法轉到定義,最終會找到PostResolveRequestCache()方法,方法里面有一句 RouteData routeData = this.RouteCollection.GetRouteData(context);

4、將RouteData與HttpContext請求封裝成一個RequestContext對象。————同樣在上述方法里面 RequestContext requestContext = new RequestContext(context, routeData);

5、根據RequestContext對象,從RouteData的RouteHandler中獲取IHttpHandler(MVC里面會有一個IHttpHandler的實現類MvcHandler)。————同樣在該方法里面 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);

執行IHttpHandler(MvcHandler)。———— context.RemapHandler(httpHandler); 將請求交給MvcHandler處理。

6、然后就是通過反射激活具體的controller,執行具體的action。————在MvcHandler的ProcessRequest()方法里面的執行邏輯。

 

筆停此處,后續再進行補充


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM