asp.net Mvc学习之URL路由

Asp.Net MVC的请求的执行过程粗略的来看大致是这样的:

 

1 WebServer 接收来自的客户端的Request(请求)

2 Web Application在第一次运行的时候(Application_Start())根据其中的设置代码会创建一个RouteTable(路由表)实现URL到处理程序之间的映射。

3 UrlRotingModule模块解析该请求的URL,并选择相关的URL路由。

4 MvcHandler对象来处理该URL路由,创建要执行的控制器(Controller )

5 执行Controller(即调用指定的执行方法)。

6 返回处理结果(执行View()方法,返回视图到浏览器)。

 

那么我们首先来深入了解一下URL路由。其实URL路由是ASP.NET 3.5 MVC框架中独立出来的一个功能,也就是说不仅仅在MVC中,即使是在传统的WebForm也可以使用它。

 

------------------------------------------------------------------------------------------------------------------------------------------

 

首先,URL路由是如何加入到HttpApplication处理管道中来的?

 

我们注意到MVC项目的WebConfig中有这么一个配置项

<add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

那么UrlRoutingModule必然是处理URL路由的一个HttpModule了,将System.Web.Routing进行反编译,并找到UrlRoutingModule这个类我们可以看到它在Init方法中注册了下面两个事件

application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);

application.PostMapRequestHandler   += new EventHandler(this.OnApplicationPostMapRequestHandler);

 

在此我们回顾下HttpApplication处理管线中的各种事件(已省略与本文无关的事件)

1   请求验证,检查浏览器发送的信息,包括确定是否包含潜在的恶意标记等。

2   如果WebConfig中配置了UrlMappingsSection,则执行URL映射

3   引发BeginRequest事件

......

......

9   引发PostResolveRequestCache事件

 

10  引发MapRequestHandler事件:  

根据所请求资源文件的扩展名(在应用程序的配置文件中映射),选择对应实现IHttpHandler接口的处理类。如果请求是aspx,并且需要对该页进行编译,则asp.net会在获取该页面实例之前对其进行编译。

 

11  引发PostMapRequestHandler事件

......

......

15 调用第10步选择的IHttpHandlerProcessRequest方法(或异步版的BeginProcessRequest

......

......

22引发EndRequest事件

 

 

上面黄色部分标明的则是UrlRoutingModule所注册的事件

 

这两个事件最开始都对HttpContext进行再次封装()

 

【关于下文要提到的RouteRouteConllectionRouteData等的简单结构说明】

 

 

附上源码:

 

  RouteData routeData = this.RouteCollection.GetRouteData(context);
        if (routeData != null)
        {
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0]));
            }
            if (!(routeHandler is StopRoutingHandler)) { RequestContext requestContext = new RequestContext(context, routeData);
                IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
                if (httpHandler == null)
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() }));
                }
                RequestData data2 = new RequestData {
                    OriginalPath = context.Request.Path,
                    HttpHandler = httpHandler
                };
                context.Items[_requestDataKey] = data2;
                context.RewritePath("~/UrlRouting.axd");
            }
        }
          

说明:

 1 PostResolveRequestCache中做了如下处理:

(1) 根据Url在路由表中查找匹配的路由获得RouteData,即:

     遍历RouteCollection集合,调用集合中Route(路由)GetRouteData方法,如果GetRouteData方法返回的RouteData对象不为空,则立即返回,这也就是说明了路由是按照路由设置的次序解析的,就算一个URL可以匹配多个路由,解析过程中一旦发现有匹配的路由就不会再进行后面的路由检索了,直接结束路由的解析过程。

 

(2) 如果最终获取到的RouteData不为空,获取RouteDataRouteHandler属性,RouteHandler是实现了IRouteHandler接口的,只有一个方法,那就是GetHttpHandler,此Handler(MvcRouteHandler)用于处理URL路由

 

(3) 如果这个RouteHandler的类型不是StopRoutingHandler

1)       封装一个RequestContext类,此类只有两个属性,一个是之前的RouteData,还有就是封装过后的contextHttpContextBase   

 

2   调用之前(2)中RouteHandler中的GetHttpHandler方法传入上一步的RequestContext参数将得到的HttpHandler,将其与当前的请求路径(context.Request.Path)一并封装到RequestData对象中,并将RequestData存入context

 

 3  重写路径,指向UrlRouting.axd文件(context.RewritePath("~/UrlRouting.axd"),此文件是类似于aspx,实现了IHttpHandler接口。

 

2   PostMapRequestHandler中做了如下处理:

 

在此事件中要替换掉context中的handler  context中获取到在之前这个PostResolveRequestCache事件中存入的RequestData,从RequestData获取OriginalPath,将开始从写路径到UrlRouting.axd改回最开始状态(即OriginalPath)让输出路径和输入路径相同,同样设置contextHandler为之前存入RequestDataHttpHandler

这个Handler在配置路由表,往RouteConllection中存入Route实例化这个Route时被传入

 

3   HttpApplication处理管道后面事件中将会调用此HandlerMvcHandler)的ProcessRequest方法。

 

 

 

写到这里我产生了一个疑问?? 

为什么在PostResolveRequestCache事件中将得到RequestData并存入Context 又在后面的PostMapRequestHandler事件中将其取出得到开始存入的Handler对象以及原始URL,并把重写的路径改回原始的URL再设置ContextHandler属性,而不是这些过程全部都在PostMapRequestHandler这个事件中处理了,这么一存一放不是多此一举吗?

 

至此我们再来研究下位于PostResolveRequestCache事件与PostMapRequestHandler事件之间的MapRequestHandler事件  

 

之前的说明是:

根据所请求资源文件的扩展名(在应用程序的配置文件中映射),选择对应实现IHttpHandler接口的处理类。如果请求是aspx,并且需要对该页进行编译,则asp.net会在获取该页面实例之前对其进行编译。

 

继续查资料可以知道

   此事件只在IIS7.0的集成模式,且.Net Framework版本大于等于3.0的情况下才会触发,由于asp.net mvc请求的路径并不会对应一个文件,所以在此处会报错,MS为了使处理模块能够在iis7中实现路由,则采取了这么一种简单的解决办法。先把路径指向~/UrlRouting.axd,在此事件中会设置一个UrlRouting.axd类型的Handler避免报错,并在下一步事件中替换掉此处的Handler再把~/UrlRouting.axd这个路径给改回来。

 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  

 

 

关于路由的使用:

(对于已存在路径的文件想要其不被路由:  

    RouteTable.Routes.RouteExistingFiles = false;

 

一般通过Global.asax文件,在Application_Start()方法设置

 

添加路由

Example:

 

routes.Add("Default", new Route

            (

                "{controller}/{action}/{id}",

                new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" }, { "id", UrlParameter.Optional } },

                new MvcRouteHandler()

            ));

                                        常量      控制器名     对应的方法     变量username     变量password

路由名

routes.Add("Register", new Route

            (

                "Register/{controller}/{action}/{username}/{password}",

                new RouteValueDictionary { { "controller", "Register" }, { "action", "RegisterUser" } },

                new MvcRouteHandler()

            ));

                                                                                  设置默认值

 

 

由于发布System.Web.Routing程序集之后MS也在不断的改进,MS感觉上述路由的设置比较麻烦,但是此程序集又已经发布了, 所以使用到.net 3.5 framework 新特性,扩展方法,即可以在原有类别中添加新的实现方法,从而实现新的功能。

位于System.Web.Mvc中的RouteCollectionExtensions类就是一个静态类,其中定义的方法就是扩展方法

针对于路由集合类RouteCollection扩展了两类方法

IgnoreRoute()

MapRoute()

 

对于MapRoute方法可以将上述代码简化为

 

routes.MapRoute(

                "Default",

                "{controller}/{action}/{id}",

                new { controller = "", action = "", id = UrlParameter.Optional }

                );

 

           routes.MapRoute(

                "Register ",

                "Register/{controller}/{action}/{username}/{password}",

                new { controller = "Register", action = "RegisterUser" }

                );

 

忽略路由

 

routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

 

在上面的路由设置其中的路由名称是可选的参数,路由名称可以用来生成URL路由,但其在路由解析中并没有作用

但是如果在视图中生成相关的路由链接,可以直接指定路由名称,加快检索速度,使用指定路由的好处还有可以不必知名路由的其他参数,例如控制器,控制方法等

 

建议: 将常用的路由存放在路由表最前端

 

自定义路由约束

 

实现一个约束类:

 

public class IDRouteConstraint: IRouteConstraint

    {

        #region IRouteConstraint Members

 

        public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)

        {

            if ((routeDirection == RouteDirection.IncomingRequest)

                && (parameterName.ToLower(CultureInfo.InvariantCulture) == "id"))

            {

                long id;

 

                if (long.TryParse(values["id"].ToString(), out id))

                {

                    if (id < 1000000 && id >= 0)

                    {

                        return true;

                    }

                }

            }

 

            return false;

        }

 

        #endregion

    }

 

 

在路由中配置

 

routes.MapRoute(

                "Default",

                "{controller}/{action}/{id}",

                new

                {

                    controller = "Home", action = "Index", id="1001"

                },

                new

                {

                    id = new IDRouteConstraint()

                }

            );

posted on 2011-08-08 09:19  阳阳多  阅读(1655)  评论(4编辑  收藏  举报