• 深入ASP.NET MVC之二:路由模块如何工作


    摘要: 上文分析了UrlRouting模块何时会被触发,本文重点分析路由模块是如何工作,以及如何利用路由模块实现Area。

    先看路由模块的PostResolveRequestCache事件中被触发的方法:

            public virtual void PostResolveRequestCache(HttpContextBase context)
            {
                RouteData routeData = this.RouteCollection.GetRouteData(context);
                if (routeData == null)
                {
                    return;
                }
                IRouteHandler routeHandler = routeData.RouteHandler;
                if (routeHandler == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
                }
                if (routeHandler is StopRoutingHandler)
                {
                    return;
                }
                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))
                {
                    context.RemapHandler(httpHandler);
                    return;
                }
                if (FormsAuthenticationModule.FormsAuthRequired)
                {
                    UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
                    return;
                }
                throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
            }

    这个方法做的工作还是比较清晰的,首先从RouteCollection中获得RouteData,从RouteData中获得RouteHandler,从RouteHandler获得httpHandler,最后调用RemapHandler将控制权交给httpHandler。UrlRoutingModule是System.Web中的通用的路由模块,并不仅限于给ASP.NET MVC使用,这里处理的今本都是针对抽象接口来处理的。后文会介绍ASP.NET MVC是如何利用这个模块实现了URL到controller/action的映射的。

    RouteCollection是一张路由表,里面包括了很多路由规则(RouteBase),RouteData则是解析好的路由,里面包括了Key-Value对的路由信息,一个routehandler。RouteCollection的GetRouteData方法,是找到符合当前请求的路由规则的RouteData,其内部实现就是遍历所有的路由规则,调用RouteBase的GetRouteData方法,返回第一个非空的RouteData。IRouteHandler 只有一个方法,

        public interface IRouteHandler
        {
            IHttpHandler GetHttpHandler(RequestContext requestContext);
        }

    但是他是从路由模块完成最重要的工作之一,等到其他模块执行完毕之后,将由这个IHttpHandler(如果其他模块没有更改这个handler)来完成接下来的请求。得到合适的HttpHandler之后,调用了HttpContext的RemapHandler方法,这个方法的核心是

    this._remapHandler = handler;

    此时尚没有真正的转交控制权,当前的handler还是global.asax中的类,在上文中有介绍到在初始化一个请求的时候,由一个StepManager来初始化需要触发的step,其中有:

     HttpApplication.IExecutionStep step = new HttpApplication.MaterializeHandlerExecutionStep(application);
     application.AddEventMapping("ManagedPipelineHandler", RequestNotification.MapRequestHandler, false, step);

    因此在MapRequestHandler的时候,会触发MaterializeHandlerExecutionStep的Execute方法,其中主要的代码是:

    if (context.RemapHandlerInstance != null)
    {
           IIS7WorkerRequest.SetScriptMapForRemapHandler();
           context.Handler = context.RemapHandlerInstance;
    }           

    在MapRequestHandler之后调用RemapHandler会导致异常,原因是改变handler的时机是在MapRequestHandler。

    下面看ASP.MVC框架是如何使用这个routing module的。MVC在RouteCollectionExtensions这个类中定义了一系列扩展方法,扩展了原有的RouteCollection类。RouteCollection类是支持ASP.NET Webform的路由模块,可以用MapPageRoute方法将url映射到aspx文件上。RouteCollectionExtensions的核心方法是:

            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;
            }

    注意Route构造函数中的第二个参数,这是一个MvcRouteHandler,这个handler也就是RouteData中的RouteHandler的值,其实现为:

            protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) {
                requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext));
                return new MvcHandler(requestContext);
            }

    暂时忽略这里的SessionStateBehavior,可以看到最终的httpHandler是MvcHandler。

    综上,routing module在PostResolveRequestCache被触发,获得RouteCollection中的Route(System.Web.Routing),解析Route数据获得RouteData,MVC框架设置RouteData中的RouteHandler为MvcRouteHandler,MvcRouteHandler的GetHttpHandler方法返回的是一个MvcHandler,这个handler最终在MapRequestHandler事件触发的时候接过request处理流程,开始处理请求,它将利用RouteData中解析好的值去触发controller/action,这个下文再介绍。ASP.NET MVC的路由模块主要是用的.NET中的System.Web.Routing.Route类来实现的,Route类的主要工作是解析路由规则,得到一个Key-Value对的序列。这是一个单纯而又复杂的过程,这里不分析其实现。

    下面简单介绍下Route类所表示的路由规则。根据上面的代码(Route的构造函数)可以看到,一条路由规则包括4个部分:Url模式,一组默认值,约束,和DataToken,DataToken是一些额外的信息,Route本身不会使用他们,但是可以提供给其他代码使用,下面会说明。 Url模式是一个字符串,包括一些固定的字符字面量和占位符,占位符由一对花括号表示{ } 。例如:

    Route definition

    Example of matching URL

    {controller}/{action}/{id}

    /Products/show/beverages

    {table}/Details.aspx

    /Products/Details.aspx

    blog/{action}/{entry}

    /blog/show/123

    {reporttype}/{year}/{month}/{day}

    /sales/2008/1/5

    {locale}/{action}

    /US/show

    {language}-{country}/{action}

    /en-US/show

    当一个url满足一个url模式的时候,Route模块会将其解析后的数据放在RouteData.Value中,例如第一条url解析之后就是如下的K-V对序列:

    controller:Products

    action:show

    id:beverages

    默认值不讨论了。

    约束是一个匿名对象,例如

    routes.MapRoute(
      "BlogArchive",
      "Archive/{entryDate}",
      new { controller = "Blog", action = "Archive" },
      new { entryDate = @"d{2}-d{2}-d{4}" }
    );

    表示对entryDate添加约束。属性的值如果是一个字符串,则代表一个正则表达式,除此之外,还可以是实现IRouteConstraint接口的对象,从而实现自定义的约束

    DataToken可以放一些自定义的数据,例如ASP.NET MVC就在其中放入了Namespace.Namespace用来区分同名的Controller,常用在Area中。Area可以将一个大型的网站划分为相对独立的区域。MSDN上的这篇文章介绍了如何创建一个Area。VS在创建一个Area的时候创建了如下结构的目录和文件:

    image

    其中Model-Controller-View的结构是和整个站点一致的,还有一个独立的Web.config文件。关键还多了一个AdminAreaRegistration文件,这个自动生成的类是用来注册Area的路由的:

            public override void RegisterArea(AreaRegistrationContext context)
            {
                context.MapRoute(
                    "Admin_default",
                    "Admin/{controller}/{action}/{id}",
                    new { action = "Index", id = UrlParameter.Optional }
                );
            }

    这个方法和普通的注册路由非常相似,看下AreaRegistrationContext.MapRoute的实现:

            public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces) {
                if (namespaces == null && Namespaces != null) {
                    namespaces = Namespaces.ToArray();
                }
    
                Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces);
                route.DataTokens["area"] = AreaName;
    
                // disabling the namespace lookup fallback mechanism keeps this areas from accidentally picking up
                // controllers belonging to other areas
                bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
                route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback;
    
                return route;
            }

    最终还是和普通的MapRoute一样,创建了一个Route对象,不同的只是给DataToken添加了area和UseNamespaceFallback属性。下面再看看这里的RegisterArea是如何被调用的,以及参数context是什么。注意到在global.asax中的Application_Start方法中第一行代码就是:

    AreaRegistration.RegisterAllAreas();

    看这个方法的实现:

    internal static void RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state) {
                List<Type> areaRegistrationTypes = TypeCacheUtil.GetFilteredTypesFromAssemblies(_typeCacheName, IsAreaRegistrationType, buildManager);
                foreach (Type areaRegistrationType in areaRegistrationTypes) {
                    AreaRegistration registration = (AreaRegistration)Activator.CreateInstance(areaRegistrationType);
                    registration.CreateContextAndRegister(routes, state);
                }
            }

    首先枚举出当前AppDomain中所有的AreaRegistration的子类,关于TypeCacheUtil,下文还会出现,暂不作介绍。然后调用CreateContextAndRegister方法,这个方法的代码如下:

            internal void CreateContextAndRegister(RouteCollection routes, object state) {
                AreaRegistrationContext context = new AreaRegistrationContext(AreaName, routes, state);
                string thisNamespace = GetType().Namespace;
                if (thisNamespace != null) {
                    context.Namespaces.Add(thisNamespace + ".*");
                }
                RegisterArea(context);
            }

    主要做了两部分事情,首先创建了AreaRegistrationContext,并且把AreaRegistration子类的命名空间加入到context的Namesapces属性中,最终调用了我们一开始谈到的自动生成的RegisterArea方法。因此,在Application_Start一开始,每个AreaRegistration的子类的RegisterArea方法都会被调用,这个方法的效果是在全局路由表中添加一条Area的路由规则,Area的路由规则默认的把当前的AreaRegistration子类的命名空间加到DataToken中,另外还有area和UseNamespaceFallback属性也加入了DataToken。至于这些值如何被使用,MVC框架是如何实现调用合适的Controller的Action方法的,下文再介绍。

  • 相关阅读:
    iOS开发——多线程篇——NSOperation(基于GCD多线程编程),下载图片并合成新图片
    iOS开发——多线程篇——GCD
    iOS开发——多线程篇——NSThread
    iOS开发——多线程篇——多线程介绍
    iOS开发——UI进阶篇(十八)核心动画小例子,转盘(裁剪图片、自定义按钮、旋转)图片折叠、音量震动条、倒影、粒子效果
    推荐系统_七月算法4月机器学习班第9次课程笔记
    STL源码分析读书笔记--第三章--迭代器(iterator)概念与traits编程技法
    C语言基础(不断更新)
    字节对齐总结
    大端模式与小端模式
  • 原文地址:https://www.cnblogs.com/yinzixin/p/2754483.html
Copyright © 2020-2023  润新知