在MVC中一個請求最終通過一個具體的HttpHandler進行處理,用於表示一個Web頁面的Page物件就是一個HttpHandler,被用於處理基於某個.aspx檔的請求。通過HttpHandler的動態映射來實現請求位址與物理檔路徑之間的分離。實際上ASP.NET路由系統就是採用了這樣的實現原理。ASP.NET路由系統通過一個註冊到當前應用的自訂HttpModule對所有的請求進行攔截,並通過對請求的分析為之動態匹配一個用於處理它的HttpHandler。HttpHandler對請求進行處理後將相應的結果寫入HTTP回復以實現對請求的相應,如下圖所示:
請求攔截器的HttpModule類型為UrlRoutingModule。UrlRoutingModule對請求的攔截是通過註冊表示當前應用的HttpApplication的PostResolveRequestCache事件實現的,看下面的代碼:
public class UrlRoutingModule : IHttpModule
{
public RouteCollection RouteCollection { get; set; }
public void Init(HttpApplication context)
{
context.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
}
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e);
}
通過RouteData的RouteHandler屬性可以得到一個IRouteHandler的路由處理器物件,而調用後者的GetHttpHandler方法直接可以取得對應的HttpHandler物件,需要Reflection(映射)到當前請求的就是這麼一個 HttpHandler。下面的程式碼呈現了定義在UrlRoutingModule的OnApplicationPostResolveRequestCache方法中的動態HttpHandler映射邏輯。
public class UrlRoutingModule : IHttpModule
{
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
HttpContextBase contextWrapper = new HttpContextWrapper(context);
RouteData routeData = this.RouteCollection.GetRouteData(contextWrapper);
RequestContext requestContext = new RequestContext(contextWrapper,
routeData);
IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
context.RemapHandler(handler);
}
}
前面的說明對於使用RouteCollection的GetRouteData取得的RouteData物件,其RouteHandler來源於配對的Route對象。對於通過調用RouteCollection的MapPageRoute方法註冊的Route來說,它的RouteHandler是一個類型為PageRouteHandler對象。
由於調用MapPageRoute方法的目的在於實現請求http address位址與某個.aspx分頁檔之間的映射,最終還是要創建的Page物件處理相應的請求,所以PageRouteHandler的GetHttpHandler方法最終返回的就是針對映射分頁檔路徑的Page物件。此外,MapPageRoute方法中還可以控制是否對物理檔位址實施授權,而授權在返回Page物件之前進行。
定義在PageRouteHandler中的HttpHandler獲取邏輯基本上體現在如下的代碼片斷中,兩個屬性VirtualPath和CheckPhysicalUrlAccess表示分頁檔的位址和是否需要對物理檔位址實施URL授權,它們在構造函數中被初始化(Initialization),而最終的來源從調用RouteCollection的MapPageRoute方法傳入的參數。
public class PageRouteHandler : IRouteHandler
{
public bool CheckPhysicalUrlAccess { get; private set; }
public string VirtualPath { get; private set; }
public PageRouteHandler(string virtualPath, bool checkPhysicalUrlAccess)
{
this.VirtualPath = virtualPath;
this.CheckPhysicalUrlAccess =
checkPhysicalUrlAccess;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
if (this.CheckPhysicalUrlAccess)
{
//確認URL的存取,在這可任意處理
}
return (IHttpHandler)BuildManger.CreateInstanceFromVirtualPath(this.VirtualPath, typeof(Page));
}
}
ASP.NET MVC的Route物件是通過調用RouteCollection的擴展方法MapRoute方法進行註冊的,它對應的RouteHandler是一個類型為MvcRouteHandler的對象。MvcRouteHandler用於獲取處理當前請求的HttpHandler是一個MvcHandler對象。MvcHandler實現對Controller的啟動、Action方法的執行以及對請求的相應,整個MVC框架實現在MvcHandler之中,如下面的程式所示:
public class MvcRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new MvcHandler(requestContext);
}
}
大致來說,整個路由系統是通過對HttpHandler的動態註冊的方式來實現的。具體來說,UrlRoutingModule通過對代表Web應用的HttpApplication的PostResolveRequestCache事件的註冊實現了對於請求的攔截。對於被攔截的請求,UrlRoutingModule利用註冊的路由表對其進行配對和解析,然後得到包含所有路由資訊的RouteData物件。最終藉由該物件的RouteHandler創建出相應的HttpHandler映射到當前請求。從可擴展性的角度可以用三種方式來實現路由方式:
• 通過集成抽象類別RouteBase創建自訂Route定義路由邏輯。
• 通過實現介面IRouteHandler創建自訂RouteHandler定制HttpHandler提供機制。
• 通過實現IHttpHandler創建自訂HttpHandler來對請求處理。
通過自訂Route對ASP.NET路由的擴展
定義在ASP.NET路由系統中預設的路由類型Route建立了定義成文本範本的URL模式與某個物理檔之間的映射,如果我們對WCF REST有一定了解的話,具體來說,WCF REST也可以借助於System.UriTemplate這個物件實現了同樣定義成某個文本範本的URI模式與目標操作之間的映射。
我們創建一個新的ASP.NET Web應用,並且添加針對程式集System.ServiceModel.dll的引用(UriTemplate定義在該程式集中),然後創建如下一個針對UriTemplate的路由類型UriTemplateRoute。
public class UriTemplateRoute:RouteBase
{
public UriTemplate UriTemplate { get; private set; }
public IRouteHandler RouteHandler { get; private set; }
public RouteValueDictionary DataTokens { get; private set; }
public UriTemplateRoute(string template, string physicalPath, object dataTokens = null)
{
this.UriTemplate = new UriTemplate(template);
this.RouteHandler = new PageRouteHandler(physicalPath);
if (null != dataTokens)
{
this.DataTokens = new RouteValueDictionary(dataTokens);
}
else
{
this.DataTokens = new RouteValueDictionary();
}
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
Uri uri = httpContext.Request.Url;
Uri baseAddress = new Uri(string.Format("{0}://{1}", uri.Scheme, uri.Authority));
UriTemplateMatch match = this.UriTemplate.Match(baseAddress, uri);
if (null == match)
{
return null;
}
RouteData routeData = new RouteData();
routeData.RouteHandler = this.RouteHandler;
routeData.Route = this;
foreach (string name in match.BoundVariables.Keys)
{
routeData.Values.Add(name,match.BoundVariables[name]);
}
foreach (var token in this.DataTokens)
{
routeData.DataTokens.Add(token.Key,
token.Value);
}
return routeData;
}
public override VirtualPathData GetVirtualPath(VRequestContext requestContext, RouteValueDictionary values)
{
Uri uri = requestContext.HttpContext.Request.Url;
Uri baseAddress = new Uri(string.Format("{0}://{1}", uri.Scheme, uri.Authority));
Dictionary<string, string> variables = new Dictionary<string, string>();
foreach(var item in values)
{
variables.Add(item.Key,
item.Value.ToString());
}
//確定路徑變數是否被提供
foreach (var name in this.UriTemplate.PathSegmentVariableNames)
{
if(!this.UriTemplate.Defaults.Keys.Any(key=>
string.Compare(name,key,true) == 0) &&
!values.Keys.Any(key=> string.Compare(name,key,true) == 0))
{
return null;
}
}
//確定查詢變數是否被提供
foreach (var name in this.UriTemplate.QueryValueVariableNames)
{
if(!this.UriTemplate.Defaults.Keys.Any(key=>
string.Compare(name,key,true) == 0) &&
!values.Keys.Any(key=> string.Compare(name,key,true) == 0))
{
return null;
}
}
Uri virtualPath = this.UriTemplate.BindByName(baseAddress,
variables);
string strVirtualPath = virtualPath.ToString().ToLower().Replace(baseAddress.ToString().ToLower(),"");
VirtualPathData virtualPathData = new VirtualPathData(this, strVirtualPath);
foreach (var token in this.DataTokens)
{
virtualPathData.DataTokens.Add(token.Key,
token.Value);
}
return virtualPathData;
}
}
UriTemplateRoute具有UriTemplate、DataTokens和RouteHandler三個唯讀屬性,前兩個通過建構函數的參數進行初始化,後者則是創建PageRouteHandler物件。
用於請求進行匹配判斷的GetRouteData方法中,可以解析出基於應用的基底位址並請求位址作為參數調用UriTemplate的Match方法,如果返回的UriTemplateMatch物件不為Null,則表示URL範本的模式與請求位址配對。在配對的情況下創建並返回相應的RouteData物件,否則直接返回Null。
用於生成URL的GetVirtualPath方法中,通過定義在URL中的範本(包括變數名包含在屬性PathSegmentVariableNames的路徑段變數和包含在QueryValueVariableNames屬性的查詢變數)是否提供RouteValueDictionary欄位或者預設變數清單(通過屬性Defaults表示)來判斷URL是否與提供的變數清單配對。在配對的情況下通過調用UriTemplate的BindByName方法得到一個完整的Uri。由於該方法返回的是相對路徑,所以需要將應用基底位址剔除,最後創建並返回一個VirtualPathData物件。如果沒有配對,則直接返回Null。
在創建的Global.asax檔中採用如下的代碼對自訂的UriTemplateRoute進行註冊,基於UriTemplate的URI範本比針對Route的URL範本,我們直接將預設值定義在範本,讓它在定義預設值方法更為直接,如下面的範例所示:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
UriTemplateRoute route = new UriTemplateRoute("{areacode=010}/{days=2}",
"~/Weather.aspx", new { defualtCity = "BeiJing", defaultDays = 2});
RouteTable.Routes.Add("default", route);
}
}
接觸MVC開發時一定會接觸到路由,那麼路由這東西的原理是如何運作呢?在web.config中有一段是這樣的:
<add assembly="System.Web.Routing, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
這一段就是路由負責的DLL組件。在這個dll中有一個很特殊的類別UrlRoutingModule,它裏面主要的核心代碼:
protected virtual void Init(HttpApplication application)
{
if (application.Context.Items[_contextKey] == null)
{
application.Context.Items[_contextKey]
= _contextKey;
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
}
}
private void
OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
this.PostResolveRequestCache(context);
}
public virtual void
PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
IRouteHandler routeHandler =
routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,
SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
}
if (!(routeHandler is StopRoutingHandler))
{
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext =
requestContext;
IHttpHandler httpHandler =
routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture,
SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[] { routeHandler.GetType() }));
}
if (httpHandler is UrlAuthFailureHandler)
{
if
(!FormsAuthenticationModule.FormsAuthRequired)
{
throw new HttpExcepion(0x191, SR.GetString("Assess_Denied_Description3"));
}
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current,
this);
}
else
{
context.RemapHandler(httpHandler);
}
}
}
}
在IHttpModule.Init中註冊了一個PostResolveRequestCache事件,而該事件主要是調用PostResolveRequestCache這個方法:
RouteData routeData = this.RouteCollection.GetRouteData(context);
IRouteHandler routeHandler =
routeData.RouteHandler;
RequestContext requestContext =
new RequestContext(context,
routeData);
context.Request.RequestContext
= requestContext;
IHttpHandler httpHandler =
routeHandler.GetHttpHandler(requestContext);
context.RemapHandler(httpHandler);
routes.MapRoute(
"Default", // 路由名稱
"{controller}/{action}/{id}", // 帶有参數的 URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 参數默認值
);
public static Route MapRoute(this RouteCollection routes, string name, string url, object defaults, object constraints, string[] namespaces) {
Route route = new Route(url, new MvcRouteHandler()) {
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;
}
各參數如下:
routeName="Default", // 路由名稱
routeUrl= "{controller}/{action}/{id}", //帶有參數的 URL
defaults=new {controller = "Home", action = "Index", id = UrlParameter.Optional} //參數默認值
constraints=null
namespaces=null
這裡建立了一個Route實例並且把它加入到RouteCollection中了。如果項目中有特殊的需要,只要我們註冊自己的IRouteHandler了,routes.Add(new Route("{controller}/{action}/{id}",new MvcRouteHandler())); 然後在裡面GetHttpHandler實現邏輯處理,就可以創建HttpHandler。
現在又回到 RouteData routeData = this.RouteCollection.GetRouteData(context);這句代碼中來,GetRouteData的程式如下:
public RouteData GetRouteData(HttpContextBase httpContext)
{
using (this.GetReadLock())
{
foreach (RouteBase base2 in this)
{
RouteData routeData =
base2.GetRouteData(httpContext);
if (routeData != null)
{
return routeData;
}
}
}
return null;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
string virtualPath =
httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) +
httpContext.Request.PathInfo;
RouteValueDictionary values = this._parsedRoute.Match(virtualPath, this.Defaults);
if (values == null) return null;
RouteData data = new RouteData(this, this.RouteHandler);
if (!this.ProcessConstraints(httpContext, values,
RouteDirection.IncomingRequest))
return null;
foreach (KeyValuePair<string, object> pair in values)
{
data.Values.Add(pair.Key, pair.Value);
}
if (this.DataTokens != null)
{
foreach (KeyValuePair<string, object> pair2 in this.DataTokens)
{
data.DataTokens[pair2.Key] =
pair2.Value;
}
}
return data;
}
以下是程式的解析,主要是幫助對於運作的程式結構上,有明確的了解。
主要下面這段,它是路由運作的監控:
RouteData data = new RouteData(this, this.RouteHandler);
RequestContext requestContext = new RequestContext(context, routeData);
context.Request.RequestContext = requestContext;
在下面程式碼中它的主要作用獲取Httphandler這個監控:
對於MvcRouteHandler就是是表示如何取得一個Httphandler,最後直接返回了一個MvcHandler實例,如下:
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
return new MvcHandler(requestContext);
}
下面這段context.RemapHandler(httpHandler); 在HttpContext的RemapHandler方法中this._remapHandler = handler;,在HttpContext中有取得這些屬性,如下所示:
internal IHttpHandler RemapHandlerInstance
{
get
{
return this._remapHandler;
}
}
在HttpApplication的內部類別MaterializeHandlerExecutionStep中的HttpApplication.IExecutionStep.Execute()方法調用下面的程式碼:
if (httpContext.RemapHandlerInstance !=
null)
{
httpContext.Handler =
httpContext.RemapHandlerInstance;
}
看到MaterializeHandlerExecutionStep這個了類別名稱,在內部類別PipelineStepManager中BuildSteps方法有HttpApplication.IExecutionStep step = new HttpApplication.MaterializeHandlerExecutionStep(app);
app.AddEventMapping("ManagedPipelineHandler", RequestNotification.MapRequestHandler, false, step);
這樣對MVC整個路由應該有個大致的理解了。
-雲遊山水為知已、逍遙一生而忘齡- 電腦神手