背景 : 對HttpModule 、HttpHandler和HttpApplication管線有一定的了解
ASP.NET MVC 應用程式的啟動流程 :
1、Application啟動時先通過RouteTable把URL映射到Handler
2、UrlRoutingModule(HttpModule)在PostResolveRequestCache事件中攔截用戶請求(在Init中註冊要攔截的事件),解析 request 並選取路由。
HttpModule 是註冊在 Web.config 中的,例如:
<configuration>
<system.web>
<httpModules>
<!-- <add name="HelloWorldModule"
type="HelloWorldModule, HelloWorldModule" /> -->
</httpModules>
</system.web>
</configuration>
可是當打開Asp.net MVc 應用程式的 Web .Config 時卻沒有發現UrlRoutingModule的配置節,原因是:"它已經默認的寫在全局的中"。應此可以在“$\Windows\Microsoft.NET\Framework\版本號\Config\Web.config“ 中找到" <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /> ”
開始自定MVC流程
using System;
using System.Web;
using System.Web.Routing;
namespace Practice_MvcModule
{
/// <summary>
/// 實作 IHttpHandler 介面來自定 HttpHandler(類似 MvcHander)
/// </summary>
public class MyTestingMvcHandler : IHttpHandler
{
public MyTestingMvcHandler(RequestContext requestContext)
{
this.RequestContext = requestContext;
}
#region IHttpHandler 的成員
public bool IsReusable
{
get { return true; }
}
// 啟用 HTTP Web 要求的處理 (Override the ProcessRequest method. )
public void ProcessRequest(HttpContext context)
{
// 簡單地把路由訊息用文字輸出到頁面上 (應該需要對 Controller 加載, 激活並執行)
context.Response.Write(String.Format("<h1>This is an HttpHandler Test.</h1><br/>{0} Controller and {1} action "
, this.RequestContext.RouteData.Values["Controller"]
, this.RequestContext.RouteData.Values["Action"]));
context.Response.End();
}
#endregion
public RequestContext RequestContext { get; private set; }
}
/// <summary>
/// 自定的處理比對路徑(類似 MvcRouteHandler)
/// </summary>
public class MyTestingRouteHandler : IRouteHandler
{
// 當路由被捕獲時, 返回一個 MyTestingMvcHandler
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MyTestingMvcHandler(requestContext);
}
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
return this.GetHttpHandler(requestContext);
}
}
/// <summary>
/// 擴展 RouteCollection, 新增自訂方法
/// 說明 :
/// 在Global.asax.cs 或 Route.Config.cs 的 RegisterRoutes 方法裡, RouteCollection 類使用的是 MapRoute 方法添加的路由,
/// 該方法是一個擴展方法, 它位於System.Web.Mvc 的 RouteCollectionExtensions 類中
/// </summary>
public static class MyTestingRouteCollectionExtensions
{
/// <summary>
/// 對應指定的 URL 路由並設定預設路由值、條件約束和命名空間
/// </summary>
/// <param name="routes">應用程式的路由集合</param>
/// <param name="name">要對應之路由的名稱</param>
/// <param name="url">路徑的 URL 模式</param>
/// <param name="defaults">包含預設路由值的物件</param>
/// <returns></returns>
public static Route MyTestingMapRoute(this RouteCollection routes, string name, string url, object defaults)
{
return MyTestingMapRoute(routes, name, url, defaults, null, null);
}
/// <param name="constraints">為 url 參數指定值的一組運算式</param>
/// <param name="namespaces">應用程式的命名空間集合</param>
public static Route MyTestingMapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
}
// 註冊 Route 和 MyTestingRouteHandler 的映射關係
Route route = new Route(url, new MyTestingRouteHandler())
{
Defaults = new RouteValueDictionary(defaults),
Constraints = new RouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
if ((namespaces != null) && (namespaces.Length > 0))
{
route.DataTokens["Namespaces"] = namespaces;
}
routes.Add(name, route);
return route;
}
}
/// <summary>
/// 自訂的 HttpModule(類似 UrlRoutingModule)
/// </summary>
public class MyTestingHttpModule : IHttpModule
{
public void Init(HttpApplication context)
{
// 在 HttpApplication 管線抵達 PostMapRequestHandler 之前要先找到處理常式
context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache);
}
void context_PostResolveRequestCache(object sender, EventArgs e)
{
// 包裝目前的 HttpContext 物件
HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context);
// 將包裝後的 HttpContext 物件傳給 RouteTable, 透過要求參數在路由表中比對路由物件, 然後回傳第一個符合的 RouteData 路由物件 (獲取路由訊息)
RouteData routeData = RouteTable.Routes.GetRouteData(context);
if (routeData == null)
{
return;
}
// 獲取 IRouteHandler 的實例 (例如 MvcRouteHandler, 或是自定的 MyTestingRouteHandler)
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException();
}
// 成功取得 RouteData 路由物件後, 建立表示目前 HttpContext 和 RouteData 的 RequestContext 物件 (建構請求上下文)
RequestContext requestContext = new RequestContext(context, routeData);
// 把 RequestContext 物件傳給 Handler 的建構式, 取得一個基於 RouteTable 的新 HttpHandler
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException("無法建立對應的 HttpHandler 物件");
}
// 將 HttpHandler 實例映射到 HttpApplication 管線中
context.RemapHandler(httpHandler);
}
public void Dispose()
{
throw new NotImplementedException();
}
}
}
新增路由規則
using System.Web.Mvc;
using System.Web.Routing;
namespace Practice_MvcModule
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MyTestingMapRoute( // MyTestingMapRoute 為自行擴展 RouteCollection 的方法
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
}
配置自定的 HttpModule
<configuration>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="MyTestingHttpModule" type="Practice_MvcModule.MyTestingHttpModule"/>
<!--
<add name="MyTestingHttpModule" type="命名空間.類別名稱[,程式集名稱(因為直接在目錄中所以此範例可以不用指定)]"/>
-->
</modules>
</system.webServer>
</configuration>
asp.net mvc 簡易流程說明:
UrlHttpModule的Init註冊了要攔截 PostResolveRequestCache 事件,在該事件中處理解析 request 並獲取 IRouteHandler 的實例 MvcRouteHandler (路由處理常式),根據 MvcRouteHandler 的 GetHttpHandler 方法獲取 IHttpHandler 的實例 MvcHandler,透過 MvcHandler 的 ProcessRequest 方法對 Controller 加載, 激活並執行。