• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

白话ASP.NET MVC之一:Url 路由

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

       好久没有写关于ASP.NET MVC的东西了,虽然《ASP.NET MVC4框架揭秘》已经完完整整的看完一遍,但是感觉和一锅粥差不多,没什么可写的,因为我自己不理解,也就写不出来。现在开始看《ASP.NET MVC5框架揭秘》,应该说第二遍了,每个代码都调试了,也看了很多的源代码,突然有一种清新的感觉,很多东西都连起来了,原来是这样啊,不不经意发出这样的感叹。既然有了一个好的理解,就整理一下,写出来,也就算巩固学习了。

      言归正传吧,很多学习Asp.Net MVC的人把整个MVC请求处理的过程人为地划分成了几个小系统,分法很多了,我比较中意划分方法的是:Url路由,Controller激活,Action执行。一个请求进来,必须要经过路由系统处理,生成必要的数据,比如:Controller的名字,Action的名字,路由系统获得了Controller的名字,才会有后面的Controller的激活系统,激活了Controller,然后执行Action,返回处理结果给客户,整个流程就结束了。但是每个部分里面又包含了很多辅助的小系统来完成相应的工作。Controller的激活和Actionde执行以后再说吧,今天我们就先来说说Url路由。

      本文章里面不打算翻译一个个大家都知道的名词,比如:Controller,Action,ModelBinder,ActionInvoker等等众多类型,直接用英文单词,因为翻译成中文有时候很难表示完整的意思。

一、简介

      Url路由:在ASP.NET MVC系统里,来自客户端的请求总是指向一个定义在某个Controller类型中的某个Action方法,并且目标Controller和Action的名称由请求的Url决定,既URL驱动的,所以必须采取某种机制根据请求的Url地址把目标的Controller和Action的名称解析出来,我们将这种机制就称为“路由(Routing)”。但是我们要说明的是这个路由系统是独立的,不是专属ASP.NET MVC的。独立的意思是可以在ASP.NET WEB FORMS里使用,可以在ASP.NET MVC里面使用,因为路由系统专门针对MVC的特点扩展了其原有的路由系统的实现。所以我把ASP.NET的路由系统分成两个部分,可能说法不太准确,我这样分是方便我更好的理解,大家可以自行分解,便于理解就好。

         第一:ASP.NET路由系统,定义在System.Web.dll程序集中,命名空间是System.Web.Routing,这个可以认为是针对ASP.NET WEB FORMS的,路由设置里面要写映射的物理.aspx文件,具体详情可以自行研究,就不多说了。

protected void Application_Start(object sender, EventArgs e)
{
      var defaults = new RouteValueDictionary{ {"name","*" }, {"id","*" } };
      RouteTable.Routes.MapPageRoute("","employees/{name}/{id}","~/Default.aspx",true,defaults);
}

         第二:针对ASP.NET MVC扩展出来的新的路由系统,定义在System.Web.MVC.dll程序集里面。扩展类是定义在命名空间System.Web.Mvc下的RouteCollectionExtensions类型,路由注册的时候要写Controller和Action了。

public static void RegisterRoutes(RouteCollection routes)
 {
       routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Employees", action = "GetAllEmployees", id = UrlParameter.Optional }
            );
 }

       我们知道ASP.NET MVC是通过扩展ASP.NET处理管道实现的,这里面有两个最重要的组件,一个是实现了IHttpModule接口的UrlRoutingModule,此组件用于截获请求,进行路由解析,并重新Remap到请求的处理程序上,这个处理程序就是第二个组件,实现了IHttpHandler的MvcHandler,此组件用于激活Controller和Action方法的执行。可以这样说,路由系统的解析操作就发生在UrlRoutingModule组件里面。我们先看看他的代码,然后我们按着请求的先后顺序一步一步的介绍所涉及到的对象。

  1 namespace System.Web.Routing
  2 {  4     public class UrlRoutingModule : IHttpModule
  5     {
  6         private static readonly object _contextKey = new object();
  7 
  8         private static readonly object _requestDataKey = new object();
  9 
 10         private RouteCollection _routeCollection;
 11 
 12         public RouteCollection RouteCollection
 13         {
 14             get
 15             {
 16                 if (this._routeCollection == null)
 17                 {
 18                     this._routeCollection = RouteTable.Routes;
 19                 }
 20                 return this._routeCollection;
 21             }
 22             set
 23             {
 24                 this._routeCollection = value;
 25             }
 26         } 31 
 32         protected virtual void Init(HttpApplication application)
 33         {
 34             if (application.Context.Items[UrlRoutingModule._contextKey] != null)
 35             {
 36                 return;
 37             }
 38             application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
 39             application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
 40         }
 41 
 42         private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
 43         {
 44             HttpContextBase context = new HttpContextWrapper(((HttpApplication)sender).Context);
 45             this.PostResolveRequestCache(context);
 46         }
 47 
 53         public virtual void PostResolveRequestCache(HttpContextBase context)
 54         {
 55             RouteData routeData = this.RouteCollection.GetRouteData(context);
 56             if (routeData == null)
 57             {
 58                 return;
 59             }
 60             IRouteHandler routeHandler = routeData.RouteHandler;
 61             if (routeHandler == null)
 62             {
 63                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
 64             }
 65             if (routeHandler is StopRoutingHandler)
 66             {
 67                 return;
 68             }
 69             RequestContext requestContext = new RequestContext(context, routeData);
 70             context.Request.RequestContext = requestContext;
 71             IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
 72             if (httpHandler == null)
 73             {
 74                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
 75                 {
 76                     routeHandler.GetType()
 77                 }));
 78             }
 79             if (!(httpHandler is UrlAuthFailureHandler))
 80             {
 81                 context.RemapHandler(httpHandler);
 82                 return;
 83             }
 84             if (FormsAuthenticationModule.FormsAuthRequired)
 85             {
 86                 UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
 87                 return;
 88             }
 89             throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
 90         }
101 } 102 }

   二、路由解析的先后顺序

         路由规则注册-----》截获请求-----》路由解析并获得RouteData对象-----》根据RouteData的RouteHandler获得MvcRouteHandler对象-----》根据MvcRouteHandler获得MvcHandler对象-----》请求对象重新路由,HttpContext.Remap(MvcHandler)-----》MvcHandler接管请求处理-----》Controller激活-----》Action方法的执行-----》返回处理结果并结束

       上面是路由解析的全过程,我再用白话描述一遍:要想解析路由,必须先注册路由规则的对象吧,也就是Route对象,连注册都没有还解析个什么劲啊,我们一般在Global.asax文件的Application_Start方法里面注册Route对象。注册好了路由规则,启动系统,早已注册好的UrlRoutingModule截获请求,用当前的请求的Url和路由表【RouteTable】里面存储的路由对象【Route】进行比较,其实是Url地址和路由对象【Route】的路由地址模板Url进行匹配,没有匹配就返回空值,如果多个匹配,就选择第一个匹配路由对象【Route】,根据选择的路由对象【Route】生成路由数据【RouteData】。因为路由数据【RouteData】包含RouteHandler属性,RouteHandler属性用于提供最终处理请求的HttpHandler,ASP.NET MVC中RouteHandler的属性值就是MvcRouteHandler,MvcRouteHandler实现了IRouteHandler接口,这个接口有一个方法GetHttpHandler,这个方法就提供了用于处理最终请求的HttpHandler,这个HttpHandler就是MvcHandler,好了,该获取的对象都准备好了,那就把请求交给MvcHandler吧,交接是通过HttpContext.Remap方实现的,好了,大概就是这么一个过程。

      我先简要的把路由解析所涉及到的类型说一下,我们是面向对象编程的,所以很多东西已经对象化了,说的不错。哈哈老王卖瓜了:

       1、Route:路由规则抽象获得RouteBase类型,此类型是抽象类,他有唯一的一个子类就是Route对象,路由规则对象肯定要有路由模板的地址Url,要有路由的默认值了,路由的约束值了等等一些东西。也可以这样理解,我们注册的每一个规则就是一个Route对象,每一个Route对象实例就是代表一种路由的规则。

       2、UrlRoutingModule:我们有了Route路由规则对象,也注册好了,系统启动,我们要把请求截获,不截获请求,就没办法处理了,所以我们就是扩展了ASP.Net处理管道,实现了IHttpModule接口,定义了UrlRoutingModule类型,它用于截获请求,进行路由解析,我上面也提到过该类,并贴出了代码,下面会详细说的,非常核心的类,如果对ASP.NET处理管道不熟悉的可以去网上查找一些资料,很容易找的。

       3、RouteTable:ASP.NET MVC有一个代表全局的路由表,所有注册Route对象都存在RouteTable.Routes表示的集合里面,路由解析的时候就是和RouteTable.Routes表示的路由表里面的每一个Route对象进行匹配,如果RouteTable里面的所有Route对象所代表的路由规则和当前的Url都不匹配就返回Null值,如果有多个匹配就选择第一个匹配的Route对象,并根据Route对象生成RouteData对象。

      4、RouteData:当请求的Url和RouteTable路由表中表示路由规则的Route相匹配的时候,会根据匹配的Route对象生成RouteData,它里面包含了根据Url解析所得到东西,如:controller的名字,Action的名字等信息。

      5、MvcRouteHandler:MvcRouteHandler是MvcHandler对象的提供对象,RouteData的RouteHandler属性的值针对MVC来说就是MvcRouteHandler,如果是ASP.NET的路由系统,那就是PageRouteHandler对象了。

      6、MvcHandler:既然我们获得了MvcHandler,通过HttpContext的Remap方法重新路由,把请求交给MvcHandler来处理,后面就是Controller激活和Action方法的解析了。

      好了,简单的说了一下每个对象的用途,大家也许有了一个大概的印象了吧,我们下面就来详细的说说每一个对象的实际情况。

三、路由对象的详解

     我们使用的是面向对象的语言,所操作的一切都是对象,路由规则经过抽象就是RouteBase对象,有了RouteBase对象,我们才可以注册路由对象,才有针对RouteBase的路由解析。接下来就让我们开始说我们的第一个对象吧,Route路由对象,刚才不是说要说RouteBase,咱们现在又要说Route对象了,怎么变了,其实没变,两个对象是一回事。RouteBase其实是 一个抽象类,我们所指的或者所说的Route路由对象,其实都是从RouteBase对象继承下来的。

      1、RouteBase和Route

        我们看看RouteBase的源代码吧,不看源代码,很多东西不能搞清楚的。

 1 public abstract class RouteBase
 2  {
 3         private bool _routeExistingFiles = true;
 4 
 5         public bool RouteExistingFiles
 6         {
 7             get
 8             {
 9                 return this._routeExistingFiles;
10             }
11             set
12             {
13                 this._routeExistingFiles = value;
14             }
15         }
16 
17         public abstract RouteData GetRouteData(HttpContextBase httpContext);
18 
19         public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
20 }

  RouteBase有两个抽象方法,第一个是返回的是RouteData类型的GetRouteData方法,RouteData说白了就是路由数据,在白点就是根据路由规则解析获得的数据,所以方法名是GetRouteData,该方法的参数是HttpContextBase对象,这个对象表示的当前请求。或者说这个方法就是根据当前的Url请求和路由规则对象进行比较,如果匹配就根据路由规则对象Route生成对象的RouteData路由数据对象。另外一个抽象方法就是,返回类型为VirtualPathData对象的GetVirtualPath方法,此方法的作用就是根据提供的数据和注册的路由规则生成相应的虚拟路径。RouteData稍后会将,让我们看看VirtualPathData是一个什么样的东西,源代码如下:

 1 public class VirtualPathData
 2 {
 3         private string _virtualPath;
 4 
 5         private RouteValueDictionary _dataTokens = new RouteValueDictionary();
 6 
 7         public RouteValueDictionary DataTokens
 8         {
 9             get
10             {
11                 return this._dataTokens;
12             }
13         }
14 
15         public RouteBase Route
16         {
17             get;
18             set;
19         }
20 
21         public string VirtualPath
22         {
23             get
24             {
25                 return this._virtualPath ?? string.Empty;
26             }
27             set
28             {
29                 this._virtualPath = value;
30             }
31         }
32 
33         public VirtualPathData(RouteBase route, string virtualPath)
34         {
35             this.Route = route;
36             this.VirtualPath = virtualPath;
37         }
38  }

返回字符串类型VirtualPath属性就是生成虚拟路径,返回类型RouteBase的Route属性表示的匹配规则那个RouteBase对象。现在我想访问真实存在的一个物理文件怎么办呢?RouteBase有一个RouteExistingFiles属性,这个属性表示是否路由物理存在的文件,默认值是True,意味着我们想访问某个物理文件在不改变设置的情况下是不行的,因为已经按着路由规则发生了路由了。

    我们在来看看Route对象吧,源码如下:

  1 public class Route : RouteBase
  2 {
  3         private const string HttpMethodParameterName = "httpMethod";
  4 
  5         private string _url;
  6 
  7         private ParsedRoute _parsedRoute;
  8 
  9         public RouteValueDictionary Constraints
 10         {
 11             get;
 12             set;
 13         }
 14 
 15         public RouteValueDictionary DataTokens
 16         {
 17             get;
 18             set;
 19         }
 20 
 21         public RouteValueDictionary Defaults
 22         {
 23             get;
 24             set;
 25         }
 26 
 27         public IRouteHandler RouteHandler
 28         {
 29             get;
 30             set;
 31         }
 32 
 33         public string Url
 34         {
 35             get
 36             {
 37                 return this._url ?? string.Empty;
 38             }
 39             set
 40             {
 41                 this._parsedRoute = RouteParser.Parse(value);
 42                 this._url = value;
 43             }
 44         }
 45 
 46         public Route(string url, IRouteHandler routeHandler)
 47         {
 48             this.Url = url;
 49             this.RouteHandler = routeHandler;
 50         }
 51 
 52         public Route(string url, RouteValueDictionary defaults, IRouteHandler routeHandler)
 53         {
 54             this.Url = url;
 55             this.Defaults = defaults;
 56             this.RouteHandler = routeHandler;
 57         }
 58 
 59         public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, IRouteHandler routeHandler)
 60         {
 61             this.Url = url;
 62             this.Defaults = defaults;
 63             this.Constraints = constraints;
 64             this.RouteHandler = routeHandler;
 65         }
 66 
 67         public Route(string url, RouteValueDictionary defaults, RouteValueDictionary constraints, RouteValueDictionary dataTokens, IRouteHandler routeHandler)
 68         {
 69             this.Url = url;
 70             this.Defaults = defaults;
 71             this.Constraints = constraints;
 72             this.DataTokens = dataTokens;
 73             this.RouteHandler = routeHandler;
 74         }
 75 
 76         public override RouteData GetRouteData(HttpContextBase httpContext)
 77         {
 78             string virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
 79             RouteValueDictionary routeValueDictionary = this._parsedRoute.Match(virtualPath, this.Defaults);
 80             if (routeValueDictionary == null)
 81             {
 82                 return null;
 83             }
 84             RouteData routeData = new RouteData(this, this.RouteHandler);
 85             if (!this.ProcessConstraints(httpContext, routeValueDictionary, RouteDirection.IncomingRequest))
 86             {
 87                 return null;
 88             }
 89             foreach (KeyValuePair<string, object> current in routeValueDictionary)
 90             {
 91                 routeData.Values.Add(current.Key, current.Value);
 92             }
 93             if (this.DataTokens != null)
 94             {
 95                 foreach (KeyValuePair<string, object> current2 in this.DataTokens)
 96                 {
 97                     routeData.DataTokens[current2.Key] = current2.Value;
 98                 }
 99             }
100             return routeData;
101         }
102 
103         public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
104         {
105             BoundUrl boundUrl = this._parsedRoute.Bind(requestContext.RouteData.Values, values, this.Defaults, this.Constraints);
106             if (boundUrl == null)
107             {
108                 return null;
109             }
110             if (!this.ProcessConstraints(requestContext.HttpContext, boundUrl.Values, RouteDirection.UrlGeneration))
111             {
112                 return null;
113             }
114             VirtualPathData virtualPathData = new VirtualPathData(this, boundUrl.Url);
115             if (this.DataTokens != null)
116             {
117                 foreach (KeyValuePair<string, object> current in this.DataTokens)
118                 {
119                     virtualPathData.DataTokens[current.Key] = current.Value;
120                 }
121             }
122             return virtualPathData;
123         }
124 
125         protected virtual bool ProcessConstraint(HttpContextBase httpContext, object constraint, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
126         {
127             IRouteConstraint routeConstraint = constraint as IRouteConstraint;
128             if (routeConstraint != null)
129             {
130                 return routeConstraint.Match(httpContext, this, parameterName, values, routeDirection);
131             }
132             string text = constraint as string;
133             if (text == null)
134             {
135                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("Route_ValidationMustBeStringOrCustomConstraint"), new object[]
136                 {
137                     parameterName,
138                     this.Url
139                 }));
140             }
141             object value;
142             values.TryGetValue(parameterName, out value);
143             string arg_7C_0 = Convert.ToString(value, CultureInfo.InvariantCulture);
144             string pattern = "^(" + text + ")$";
145             return Regex.IsMatch(arg_7C_0, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
146         }
147 
148         private bool ProcessConstraints(HttpContextBase httpContext, RouteValueDictionary values, RouteDirection routeDirection)
149         {
150             if (this.Constraints != null)
151             {
152                 foreach (KeyValuePair<string, object> current in this.Constraints)
153                 {
154                     if (!this.ProcessConstraint(httpContext, current.Value, current.Key, values, routeDirection))
155                     {
156                         return false;
157                     }
158                 }
159                 return true;
160             }
161             return true;
162         }
163   }

  其实代码不复杂,大家也应该看的懂,Route对象直接继承RouteBase对象的,而且是唯一一个这样的对象,既然是路由规则对象,肯定包括,地址模板,默认值,约束条件和一些附加的数据,Constraints保存的就是约束条件,Defaults保存的就是默认值,Url属性就是地址模板了。他一定要实现GetRouteData方法和GetVirtualPath方法

 2、RouteData

    我们有了路由规则了,也就是Route对象,我们也注册了,接下来就是路由解析,就是和Route对象的的Url进行比较,如果匹配就生成了RouteData对象,也就是Route对象GetRouteData方法返回结果了。大家一定要记住,RouteData是基于Route对象生成的,我们看看源码吧:

 1 public  
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap