• 【源码】进入ASP.NET MVC流程的大门


    UrlRoutingModule的功能

    在ASP.NET MVC的请求过程中,UrlRoutingModule的作用是拦截当前的请求URL,通过URL来解析出RouteData,为后续的一系列流程提供所需的数据,比如ControllerAction等等。其实,UrlRoutingModule和我们自定义的HttpModule都是实现了IHttpModule接口的两个核心方法,Init方法和Dispose方法。下面是MVC中实现UrlRoutingModule代码。首先,在初始化的方法中检查该Module是否被加入到了当前请求的请求管道,然后注册了管道事件中的PostResolveRequestCache事件。其实最理想的注册事件应该是MapRequestHandler事件,但是为了考虑到兼容性(IIS 6 和 IIS 7 ISAPI模式下不可用),微软选择了紧邻MapRequestHandler事件之前的PostResolveRequestCache事件。

     1 protected virtual void Init(HttpApplication application)
     2 {
     3     // 检查 UrlRoutingModule 是否已经被加入到请求管道中
     4     if (application.Context.Items[_contextKey] != null)
     5     {
     6         // 已经添加到请求管道则直接返回
     7         return;
     8     }
     9     application.Context.Items[_contextKey] = _contextKey;
    10 
    11     // 理想情况下, 我们应该注册 MapRequestHandler 事件。但是,MapRequestHandler事件在
    12     // II6 或 IIS7 ISAPI 模式下是不可用的,所以我们注册在 MapRequestHandler 之前执行的事件,
    13     // 该事件适用于所有的IIS版本。
    14     application.PostResolveRequestCache += OnApplicationPostResolveRequestCache;
    15 }

    在注册事件中,将HttpApplication的请求上下文HttpContext做了一个封装,因为HttpContext是没有基类的,也不是Virtual的,所以没办法做单元测试,也就是不可Mock的,所以针对HttpContext做了一个封装。HttpContextBaseHttpContextWrapper的基类,真正封装HttpContext的就是HttpContextWrapper,所以三者之间的关系就是这样的。完成封装以后开始执行PostResolveRequestCache方法,并将封装好的请求上下文作为参数传入。

    1 private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
    2 {
    3     //HttpContextWrapper继承自HttpContextBase,用于替换HttpContext以实现可测试的接口
    4     HttpApplication app = (HttpApplication)sender;
    5     HttpContextBase context = new HttpContextWrapper(app.Context);
    6     PostResolveRequestCache(context);
    7 }

    进入PostResolveRequestCache事件后,UrlRoutingModule开始真正的工作,该方法是处理URL的核心方法。根据当前请求的上下文,去匹配路由表是否存在与之匹配的URL,如果匹配则从路由信息中获取RouteData,以及IRouteHandler。拿到IRouteHandler后,要进行一些列的判断,比如要判断是否是StopRoutingHandler,在Global.asax文件中有一段RouteConfig.RegisterRoutes(RouteTable.Routes);代码,在这个RegisterRoutes方法中有一句routes.IgnoreRoute("{resource}.axd/{*pathInfo}");表示需要过滤掉的路由,而这个IgnoreRoute路由的Handler就是StopRoutingHandler,所以在这里做了判断,Handler是StopRoutingHandler则不往下执行,直接return,不再处理这条请求,如果是则路由模块会停止继续处理请求,如果不是则继续处理,并根据RequestContext来获取IHttpHandler,拿到IHttpHandler后还要继续验证是不是UrlAuthFailureHandlerUrlAuthFailureHandler也实现了IHttpHandler,当当前请求无权限时,用于返回401错误。

     1 public virtual void PostResolveRequestCache(HttpContextBase context)
     2 {
     3     // 匹配传入的URL,检查路由表中是否存在与之匹配的URL
     4     RouteData routeData = RouteCollection.GetRouteData(context);
     5 
     6     // 如果没有找到匹配的路由信息,直接返回
     7     if (routeData == null)
     8     {
     9         return;
    10     }
    11 
    12     // 如果找到的匹配的路由,则从路由信息的RouteHandler中获取IHttpHandler
    13     IRouteHandler routeHandler = routeData.RouteHandler;
    14     if (routeHandler == null)
    15     {
    16         throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, SR.GetString(SR.UrlRoutingModule_NoRouteHandler)));
    17     }
    18 
    19     // 如果该IRouteHandler是StopRoutingHandler,路由模块会停止继续处理该请求
    20     // routes and to let the fallback handler handle the request.
    21     if (routeHandler is StopRoutingHandler)
    22     {
    23         return;
    24     }
    25 
    26     RequestContext requestContext = new RequestContext(context, routeData);
    27 
    28     // 将路由信息添加到请求上下文
    29     context.Request.RequestContext = requestContext;
    30 
    31     IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
    32     if (httpHandler == null)
    33     {
    34         throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, SR.GetString(SR.UrlRoutingModule_NoHttpHandler), routeHandler.GetType()));
    35     }
    36 
    37     // 如果该IHttpHandler是认证失败的IHttpHandler,返回401权限不足错误
    38     if (httpHandler is UrlAuthFailureHandler)
    39     {
    40         if (FormsAuthenticationModule.FormsAuthRequired)
    41         {
    42             UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
    43             return;
    44         }
    45         else
    46         {
    47             throw new HttpException(401, SR.GetString(SR.Assess_Denied_Description3));
    48         }
    49     }
    50 
    51     // Remap IIS7 to our handler
    52     context.RemapHandler(httpHandler);
    53 }

    如果请求认证失败,返回401错误,并且调用CompleteRequest方法,显式地完成当前请求。

     1 internal static void ReportUrlAuthorizationFailure(HttpContext context, object webEventSource) 
     2 {
     3     // 拒绝访问
     4     context.Response.StatusCode = 401;
     5     WriteErrorMessage(context);
     6 
     7     if (context.User != null && context.User.Identity.IsAuthenticated) {
     8         // 这里AuditUrlAuthorizationFailure指示在Web请求过程中URL授权失败的事件代码
     9         WebBaseEvent.RaiseSystemEvent(webEventSource, WebEventCodes.AuditUrlAuthorizationFailure);
    10     }
    11     context.ApplicationInstance.CompleteRequest();
    12 }

    方法GetRouteData的作用是根据当前请求的上下文来获取路由数据,在匹配RouteCollection集合之前,会检查当前的请求是否是静态文件,如果请求的是存在于服务器上的静态文件则直接返回,否则继续处理当前请求。

     1 public RouteData GetRouteData(HttpContextBase httpContext) 
     2 {
     3     if (httpContext == null) 
     4     {
     5         throw new ArgumentNullException("httpContext");
     6     }
     7     if (httpContext.Request == null) 
     8     {
     9         throw new ArgumentException(SR.GetString(SR.RouteTable_ContextMissingRequest), "httpContext");
    10     }
    11 
    12     // Optimize performance when the route collection is empty.当路由集合是空的的时候优化性能.  The main improvement is that we avoid taking
    13     // a read lock when the collection is empty.主要的改进是当集合为空的时候避免添加只读锁。  Without this check, the UrlRoutingModule causes a 25%-50%
    14     // 没有这个检查的话,UrlRoutingModule 性能会因为锁的缘故而下降25%-50%
    15     // regression in HelloWorld RPS due to lock contention.  The UrlRoutingModule is now in the root web.config,
    16     // UrlRoutingModule目前被配置在根目录的web.config
    17     // so we need to ensure the module is performant, especially when you are not using routing.
    18     // 所以我们应该确认下这个module是否是高效的,尤其是当没有使用路由的时候。
    19     // This check does introduce a slight bug, in that if a writer clears the collection as part of a write
    20     // transaction, a reader may see the collection when it's empty, which the read lock is supposed to prevent.
    21     // We will investigate a better fix in Dev10 Beta2.  The Beta1 bug is Dev10 652986.
    22     if (Count == 0) {
    23         return null;
    24     }
    25 
    26     bool isRouteToExistingFile = false;
    27     // 这里只检查一次
    28     bool doneRouteCheck = false; 
    29     if (!RouteExistingFiles) 
    30     {
    31         isRouteToExistingFile = IsRouteToExistingFile(httpContext);
    32         doneRouteCheck = true;
    33         if (isRouteToExistingFile) 
    34         {
    35             // If we're not routing existing files and the file exists, we stop processing routes
    36             // 如果文件存在,但是路由并没有匹配上,则停止继续处理当前请求。
    37             return null;
    38         }
    39     }
    40 
    41     // Go through all the configured routes and find the first one that returns a match
    42     // 遍历所有已配置的路由并且返回第一个与之匹配的
    43     using (GetReadLock())
    44     {
    45         foreach (RouteBase route in this)
    46         {
    47             RouteData routeData = route.GetRouteData(httpContext);
    48             if (routeData != null)
    49             {
    50                 // If we're not routing existing files on this route and the file exists, we also stop processing routes
    51                 if (!route.RouteExistingFiles)
    52                 {
    53                     if (!doneRouteCheck)
    54                     {
    55                         isRouteToExistingFile = IsRouteToExistingFile(httpContext);
    56                         doneRouteCheck = true;
    57                     }
    58                     if (isRouteToExistingFile)
    59                     {
    60                         return null;
    61                     }
    62                 }
    63                 return routeData;
    64             }
    65         }
    66     }
    67     return null;
    68 }

    下面这段代码就是获取相对路径来检测文件夹和文件是否存在,存在返回true,否则返回false

    1 // 如果当前请求的是一个存在的文件,则返回true
    2 private bool IsRouteToExistingFile(HttpContextBase httpContext)
    3 {
    4     string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath;
    5     return ((requestPath != "~/") &&
    6         (VPP != null) &&
    7         (VPP.FileExists(requestPath) ||
    8         VPP.DirectoryExists(requestPath)));
    9 }

    如果文中有表述不正确或有疑问的可以在评论中指出,一起学习一起进步!!

  • 相关阅读:
    GCD (hdu 5726)
    1092
    D. Puzzles(Codeforces Round #362 (Div. 2))
    A. Lorenzo Von Matterhorn
    Polyomino Composer(UVA12291)
    Optimal Symmetric Paths(UVA12295)
    菜鸟物流的运输网络(计蒜客复赛F)
    1193
    1119
    1374
  • 原文地址:https://www.cnblogs.com/xhb-bky-blog/p/9235086.html
Copyright © 2020-2023  润新知