但是在匹配这个之前,MVC首先要接管请求才能处理,也就是说我们要有对应MVC的HttpHandler(后面知道它的名字叫MvcHandler)被MapRequestHandler周期的处理引擎查找到并且应用上才行,然后后面才能由 Controller/Action执行。另外一方面,由于该URL地址没有扩展名,所以无法进入ASP.NET的RunTime,MVC2的实现方式是:注册通配符(*.*)映射到aspnet_ISPAI.dll,然后通过一个自定义的UrlRoutingModuel来匹配Route规则,再继续处理,但是MVC3的时候,匹配Route规则的处理机制集成到ASP.NET4.0里了,也就是今天我们这篇文章所要讲的主角(UrlRoutingModule)的处理机制。
先来看UrlRoutingModule的源码,无容置疑地这个类是继承于IHttpModule,首先看一下Init方法的代码:
protected virtual void Init(HttpApplication application) {
//////////////////////////////////////////////////////////////////
// Check if this module has been already addded
if (application.Context.Items[_contextKey] != null) {
return; already added to the pipeline
}
application.Context.Items[_contextKey] = _contextKey;
Ideally we would use the MapRequestHandler event. However,MapRequestHandler is not available
in II6 or IIS7 ISAPI Mode. Instead,we use PostResolveRequestCache,which is the event immediately
before MapRequestHandler. This allows use to use one common codepath for all versions of IIS.
application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
}
该代码在PostResolveRequestCache周期事件上添加了我们需要执行的方法,用于URL匹配规则的设置,但是为什么要在这个周期点上添加事件呢?看了注释,再结合我们前面对Pipeline的了解,释然了,要像动态注册自己的HttpHandler,那就需要在MapRequestHandler之前进行注册自己的规则(因为这个周期点就是做这个事情的),但由于IIS6不支持这个事件,所以为了能让IIS6也能运行MVC3,所以我们需要在这个周期之前的PostResolveRequestCache的事件点上去注册我们的规则,也许如果IIS6被微软废弃以后,就会将这个事件添加到真正的开始点MapRequestHandler上哦。
我们继续来看注册该事件的OnApplicationPostResolveRequestCache方法的代码:
public void PostResolveRequestCache(HttpContextBase context) {
Match the incoming URL against the route table
RouteData routeData = RouteCollection.GetRouteData(context); Do nothing if no route found
if (routeData == null) {
return;
}
If a route was found,get an IHttpHandler from the route's RouteHandler
IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == throw new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,SR.GetString(SR.UrlRoutingModule_NoRouteHandler)));
}
This is a special IRouteHandler that tells the routing module to stop processing
routes and to let the fallback handler handle the request.
if (routeHandler is StopRoutingHandler) {
return;
}
RequestContext requestContext = new RequestContext(context,routeData);
Dev10 766875 Adding RouteData to HttpContext
context.Request.RequestContext = requestContext;
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == new InvalidOperationException(
String.Format(
CultureInfo.CurrentUICulture,SR.GetString(SR.UrlRoutingModule_NoHttpHandler),routeHandler.GetType()));
}
if (httpHandler is UrlAuthFailureHandler) {
if (FormsAuthenticationModule.FormsAuthRequired) {
UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current,255); line-height:1.5!important">this);
return;
}
else {
new HttpException(401,SR.GetString(SR.Assess_Denied_Description3));
}
}
Remap IIS7 to our handler
context.RemapHandler(httpHandler);
}
我已经加粗了4行重要的代码,第一行是通过传递HttpContext参数,从RouteCollection找到对应的静态属性RouteData( GetRouteData方法里会先判断真实文件是否存在,如果不存在才去找RouteData),第二行然后从RouteData的属性RouteHandler获取一个IRouteHandler的实例,第三行是从该实例里获取对应的IHttpHandler实例,第4行是调用HttpContext的RemapHandler方法重新map新的handler(这行代码的注释虽然说是remap IIS7,其实IIS6也是用了,只不过判断该方法里对IIS7集成模式多了一点特殊处理而已),然后可以通过HttpContext. RemapHandlerInstance属性来得到这个实例。
关于Route/RouteData/RouteCollection/IRouteHandler的作用主要就是定义URL匹配到指定的IHttpHandler,然后注册进去,具体实现我们稍后再讲,现在先看一下Http Pipeline里是如何找到这个IHttpHandler实例的,由于IIS6和IIS7集成模式是差不多的,前面的文章我们提到了都是最终调用到IHttpHandlerFactory的实例,然后从中获取IHttpHandler,所以我们这里只分析IIS6和IIS7经典模式的实现。
先来看BuildSteps里查找HttpHandler的方法MapHandlerExecutionStep的代码,只有几行代码,最重要的是:
context.Handler = _application.MapHttpHandler(
context,request.RequestType,request.FilePathObject,request.PhysicalPathInternal,255); line-height:1.5!important">false /*useAppConfig*/);
MapHttpHandler就是我们要查找Handler的方法了,来仔细看看代码:
internal IHttpHandler MapHttpHandler(HttpContext context,String requestType,VirtualPath path,String pathTranslated,bool useAppConfig) {
Don't use remap handler when HttpServerUtility.Execute called
IHttpHandler handler = (context.ServerExecuteDepth == 0) ? context.RemapHandlerInstance : null; using (new ApplicationImpersonationContext()) {
Use remap handler if possible
if (handler != null){
return handler;
}
Map new handler
HttpHandlerAction mapping = GetHandlerMapping(context,requestType,path,useAppConfig);
If a page developer has removed the default mappings with <httpHandlers><clear>
without replacing them then we need to give a more descriptive error than
a null parameter exception.
if (mapping == null) {
PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_NOT_FOUND);
PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_FAILED);
new HttpException(SR.GetString(SR.Http_handler_not_found_for_request_type,requestType));
}
Get factory from the mapping
IHttpHandlerFactory factory = GetFactory(mapping);
Get factory from the mapping
try {
Check if it supports the more efficient GetHandler call that can avoid
a VirtualPath object creation.
IHttpHandlerFactory2 factory2 = factory as IHttpHandlerFactory2;
if (factory2 != null) {
handler = factory2.GetHandler(context,pathTranslated);
}
else {
handler = factory.GetHandler(context,path.VirtualPathString,pathTranslated);
}
}
catch (FileNotFoundException e) {
if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated))
404,255); line-height:1.5!important">null,e);
else
null);
}
catch (DirectoryNotFoundException e) {
if (HttpRuntime.HasPathDiscoveryPermission(pathTranslated))
else
catch (PathTooLongException e) {
414,255); line-height:1.5!important">null);
}
Remember for recycling
if (_handlerRecycleList == null)
_handlerRecycleList = new ArrayList();
_handlerRecycleList.Add(new HandlerWithFactory(handler,factory));
}
return handler;
}
从代码可以看出,首先如果当前页面使用了HttpServerUtility.Execute进行页面内跳转,就不使用我们通过路由设置的HttpHandler(也就是HttpContent.RemapHandlerInstance属性),如果没有跳转,就使用,并且优先级是第一的,只有当不设置任何基于Route的HttpHandler,才走剩余的匹配规则(也就是之前ASP.NET默认的按照扩展名类匹配的,这部分和我们关系不大就不做详细分析了)。
好了,知道了UrlRouteModuel的大概机制,我们再回头看看如何通过Route/RouteData/RouteCollection/IRouteHandler这几个类来实现动态注册Route规则的,先来看Route的代码:
[TypeForwardedFrom(System.Web.Routing,Version=3.5.0.0,Culture=Neutral,PublicKeyToken=31bf3856ad364e35")]
class Route : RouteBase
{
public Route(string url,IRouteHandler routeHandler)
{
Url = url;
RouteHandler = routeHandler;
}
url;
Defaults = defaults;
Constraints = constraints;
RouteHandler = routeHandler;
}
省略部分代码
override RouteData GetRouteData(HttpContextBase httpContext)
{
Parse incoming URL (we trim off the first two chars since they're always "~/")
string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
RouteValueDictionary values = _parsedRoute.Match(requestPath,Defaults);
if (values == null)
{
If we got back a null value set,that means the URL did not match
return null;
}
RouteData routeData = new RouteData(this,RouteHandler);
Validate the values
if (!ProcessConstraints(httpContext,values,RouteDirection.IncomingRequest)) {
null;
}
Copy the matched values
foreach (var value in values) {
routeData.Values.Add(value.Key,value.Value);
}
Copy the DataTokens from the Route to the RouteData
if (DataTokens != null) {
var prop in DataTokens) {
routeData.DataTokens[prop.Key] = prop.Value;
}
}
return routeData;
}
}
Route代码提供了一系列的构造函数重载(我们这里只列出了两个),构造函数主要是传入URL和对应的IRouteHandler实例以及约束规则(比如正则等),然后提供了一个最重要的GetRouteData方法,用于将Route自身和IRouteHandler组装成RouteData,然后返回(中途也会验证相应的约束条件,比如是否符合某个正则表达式),RouteData类本身没有什么逻辑,只是暴露了Route和RouteHandler属性。
我们再来看RouteCollection,该类保存了所有的Route规则(即URL和对应的IRouteHandler),通过静态属性RouteTable.Routes来获取RouteCollection实例,通过UrlRoutingModule里暴露的RouteCollection属性我们可以验证这一点:
public RouteCollection RouteCollection {
get {
if (_routeCollection == null) {
_routeCollection = RouteTable.Routes;
}
return _routeCollection;
}
set {
_routeCollection = value;
}
}
还有一个需要注意的,RouteHandler继承的IRouteHandler的代码:
interface IRouteHandler
{
IHttpHandler GetHttpHandler(RequestContext requestContext);
}
该代码只提供了一个GetHttpHandler方法,所有实现这个接口的类需要实现这个方法,MVCHandler就是这么实现的(下一章节我们再细看)。
至此,我们应该有一个清晰的认识了,我们通过全局静态属性集合(RouteTable.Routes)去添加各种各样的Route(但应该在HttpModule初始化周期之前),然后通过UrlRoutingModule负责注册Route以及对应的IRouteHandler实例(IRouteHandler实例可以通过GetHttpHandler获取IHttpHandler),最终实现根据不同URL来接管不同的HttpHandler。
MVC正是利用HttpApplication创建的周期(Application_Start方法)来添加了我们所需要的Route规则,当然在添加规则的时候带上了MVCHandler这个重要的HttpHandler,
代码如下:
void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute(
UrlParameter.Optional }
);
}
MapRoute方法是一个扩展方法,通过该扩展方法注册Route是个不错的方法,下一章节,我们讲讲解MVC是如何注册自己的MVCRouteHandler实例以及如何实现MVCHandler的调用的。
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!