ASP.NET MVC是如何运行的[2]: URL路由

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析DNS,个人版 1个月
简介:

在一个ASP.NET MVC应用来说,针对HTTP请求的处理和相应定义Controller类型的某个Action方法中,每个HTTP请求的目标对象不再像ASP .NET Web Form应用一样是一个物理文件,而是某个Controller的某个Action。目标Controller和Action的名称包含在HTTP请求中,而ASP.NET MVC的首要任务就是通过当前HTTP请求的解析得到正确的Controller和Action的名称。这个过程是通过ASP.NET MVC的URL路由机制来实现的。

一、RouteData

ASP.NET定义了一个全局的路由表,路由表中的每个路由对象对应着一个将Controller和Action名称作为站位符的URL模板。对于每一个抵达的HTTP请求,ASP.NET MVC会遍历路由表找到一个URL模板的模式与请求地址相匹配的路有对象,并最终解析出以Controller和Action名称为核心的路由数据。在我们自定义的ASP.NET MVC框架中,路由数据通过具有如下定义的RouteData类型表示。

   1: public class RouteData
   2: {
   3:     public IDictionary<string, object> Values { get; private set; }
   4:     public IDictionary<string, object> DataTokens { get; private set; }
   5:     public IRouteHandler RouteHandler { get;  set; }
   6:     public RouteBase Route { get; set; }
   7:  
   8:     public RouteData()
   9:     {
  10:         this.Values = new Dictionary<string, object>();
  11:         this.DataTokens = new Dictionary<string, object>();
  12:         this.DataTokens.Add("namespaces", new List<string>());
  13:     }
  14:     public string Controller
  15:     {
  16:         get
  17:         {
  18:             object controllerName = string.Empty;
  19:             this.Values.TryGetValue("controller", out controllerName);
  20:             return controllerName.ToString();
  21:         }
  22:     }
  23:     public string ActionName
  24:     {
  25:         get
  26:         {
  27:             object actionName = string.Empty;
  28:             this.Values.TryGetValue("action", out actionName);
  29:             return actionName.ToString();
  30:         }
  31:     } 
  32:     public IEnumerable<string> Namespaces
  33:     {
  34:         get
  35:         {
  36:             return (IEnumerable<string>)this.DataTokens["namespaces"];
  37:         }
  38:     } 
  39: }

从上面的代码片断所示,RouteData定义了两个字典类型的属性Values和DataTokens,前者代表直接从请求地址解析出来的变量,后者代表其他类型的变量。表示Controller和Action名称的同名属性直接从Values字典中提取,对应的Key分别为controller和action。属性Namespaces表示辅助Controller类型的解析而设置的命名空间列表,该属性值从DataTokens字典中提取,对应的Key为namespaces。

我们之前已经提到过ASP.NET MVC本质上是两个自定义的ASP.NET组件来实现的,一个是自定义的HttpModule,另一个是自定义的HttpHandler,而后者从RouteData的RouteHandler属性获得。RouteData的RouteHandler属性类型为IRouteHandler接口,如下面的代码片断所示,该接口具有一个唯一的GetHttpHandler用于返回真正用于处理HTTP请求的HttpHandler对象。

   1: public interface IRouteHandler
   2: {
   3:     IHttpHandler GetHttpHandler(RequestContext requestContext);
   4: }

IRouteHandler接口的GetHttpHandler方法接受一个类型为RequestContext的参数。顾名思义,RequestContext表示当前(HTTP)请求的上下文,其核心就是对当前HttpContext和RouteData的封装,这可以通过如下的代码片断看出来。

   1: public class RequestContext
   2: {
   3:     public virtual HttpContextBase HttpContext { get; set; }
   4:     public virtual RouteData RouteData { get; set; }
   5: }

二、Route和RouteTable

RouteData具有一个类型为RouteBase的Route属性,表示当前路由表中与当前请求匹配的路由对象。换句话说,当前的RouteData就是通过该路由对象针对当前HTTP请求进行解析获得的。RouteBase是一个抽象类,如下面的代码片断所示,它仅仅包含一个GetRouteData方法,该方法通过对以HttpContextBase对象表示的当前HTTP上下文进行解析从而获取一个RouteData对象。

   1: public abstract class RouteBase
   2: {
   3:     public abstract RouteData GetRouteData(HttpContextBase httpContext);
   4: }

ASP.NET MVC提供的基于URL模板的路由机制是通过具有如下定义的Route类型实现的。Route是RouteBase的子类,字符串类型的Url属性代表定义的URL模板 。在实现的GetRouteData方法中,通过HttpContextBase获取相对请求地址,如果该地址与定义在模板中的URL模式相匹配则创建一个RouteData返回;否则返回Null。对于返回的RouteData对象,其Values属性表示的字典包含直接通过地址解析出来的变量,而对于DataTokens字典和RouteHandler属性,则直接取自Route对象的同名属性。

   1: public class Route : RouteBase
   2: {
   3:     public IRouteHandler RouteHandler { get; set; }
   4:     public Route()
   5:     {
   6:         this.DataTokens = new Dictionary<string, object>();
   7:         this.RouteHandler = new MvcRouteHandler();
   8:     }
   9:     public override RouteData GetRouteData(HttpContextBase httpContext)
  10:     {
  11:         IDictionary<string, object> variables;
  12:         if (this.Match(httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2), out variables))
  13:         {
  14:             RouteData routeData = new RouteData();
  15:             foreach (var item in variables)
  16:             {
  17:                 routeData.Values.Add(item.Key, item.Value);
  18:             }
  19:             foreach (var item in DataTokens)
  20:             {
  21:                 routeData.DataTokens.Add(item.Key, item.Value);
  22:             }
  23:             routeData.RouteHandler = this.RouteHandler;
  24:             return routeData;
  25:         }
  26:         return null;
  27:     }
  28:     public string Url { get; set; }
  29:     public IDictionary<string, object> DataTokens { get; set; }
  30:     protected bool Match(string requestUrl, out IDictionary<string,object> variables)
  31:     {
  32:         variables = new Dictionary<string,object>();
  33:         string[] strArray1 = requestUrl.Split('/');
  34:         string[] strArray2 = this.Url.Split('/');
  35:         if (strArray1.Length != strArray2.Length)
  36:         {
  37:             return false;
  38:         }
  39:  
  40:         for (int i = 0; i < strArray2.Length; i++)
  41:         { 
  42:             if(strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}"))
  43:             {
  44:                 variables.Add(strArray2[i].Trim("{}".ToCharArray()),strArray1[i]);
  45:             }
  46:         }
  47:         return true;
  48:     }
  49: }

由于同一个Web应用可以采用多种不同的URL模式,所以也需要注册多个继承自RouteBase的路由对象对它们进行解析,多个路由对象组成了一个路由表。在我们自定义ASP.NET MVC框架中,路由表通过类型RouteTable表示。如下面的代码片断所示,RouteTable仅仅具有一个类型为RouteDictionary的Routes属性表示针对真个Web应用的全局路由表。

   1: public class RouteTable
   2: {
   3:     public static RouteDictionary Routes { get; private set; }
   4:     static RouteTable()
   5:     {
   6:         Routes = new RouteDictionary();
   7:     }
   8: }

RouteDictionary表示一个具名的路由对象的列表,我们直接让它继承自Dictionary<string, RouteBase>类型,其中的Key表示注册的路由对象的名称。在GetRouteData方法中,我们遍历集合找到与指定的HttpContextBase对象匹配的路由对象,并得到对应的RouteData。

   1: public class RouteDictionary: Dictionary<string, RouteBase>
   2: {
   3:     public RouteData GetRouteData(HttpContextBase httpContext)
   4:     {
   5:         foreach (var route in this.Values)
   6:         {
   7:             RouteData routeData = route.GetRouteData(httpContext);
   8:             if (null != routeData)
   9:             {
  10:                 return routeData;
  11:             }
  12:         }
  13:         return null;
  14:     }
  15: }

在Global.asax中我们创建了一个基于指定URL模板的Route对象,并将其添加到通过RouteTable的静态只读属性Routes表示的全局路由表中。

三、UrlRoutingModule

路由表的目的在于对当前的HTTP请求进行解析从而获取一个以Controller和Action名称为核心的路由数据,即上面介绍的RouteData,而整个解析工作是通过一个类型为UrlRoutingModule的自定义HttpModule来完成的。如下面的代码片断所示,在实现了接口IHttpModule的UrlRoutingModule类型的Init方法中,我们注册了HttpApplicataion的PostResolveRequestCache事件。

   1: public class UrlRoutingModule: IHttpModule
   2: {
   3:     public void Dispose()
   4:     {}
   5:     public void Init(HttpApplication context)
   6:     {
   7:         context.PostResolveRequestCache += OnPostResolveRequestCache;
   8:     }
   9:     protected virtual void OnPostResolveRequestCache(object sender, EventArgs e)
  10:     {
  11:         HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
  12:         RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
  13:         if (null == routeData)
  14:         {
  15:             return;
  16:         }
  17:         RequestContext requestContext = new RequestContext { RouteData = routeData, HttpContext = httpContext };
  18:         IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
  19:         httpContext.RemapHandler(handler); 
  20:     }
  21: }

当PostResolveRequestCache事件触发之后,UrlRoutingModule通过RouteTable的静态只读属性Routes得到表示全局路由表的RouteDictionary对象,然后调用其GetRouteData方法并传入用于封装当前HttpContext的HttpContextWrapper对象(HttpContextWrapper是HttpContextBase的子类)并得到一个封装了路由数据的RouteData对象。如果得到的RouteData不为空,根据该对象本身和和之前得到的HttpContextWrapper对象创建一个表示当前请求上下文的RequestContext对象,将其作为参数传入RouteData的RouteHandler的GetHttpHandler方法得到一个HttpHandler对象。最后我们调用HttpContextWrapper对象的RemapHandler方法对得到HttpHandler进行映射,这意味着该HttpHandler将最终用于处理当前的HTTP请求。

ASP.NET MVC是如何运行的[1]: 建立在“伪”MVC框架上的Web应用
ASP.NET MVC是如何运行的[2]: URL路由
ASP.NET MVC是如何运行的[3]: Controller的激活
ASP.NET MVC是如何运行的[4]: Action的执行


作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
4月前
|
移动开发 前端开发 JavaScript
前端vue2、vue3去掉url路由“ # ”号——nginx配置(一)
前端vue2、vue3去掉url路由“ # ”号——nginx配置
282 0
|
17天前
|
Python
路由(URL routing)
【8月更文挑战第23天】
25 4
|
8天前
|
API 开发者 Python
"FastAPI路由大揭秘!轻松玩转URL映射,让你的Web应用路由设计既RESTful又灵活多变,秒杀传统框架的秘籍在这里!"
【8月更文挑战第31天】在Web开发中,路由是连接用户请求与后端逻辑的关键。FastAPI作为现代Python Web框架的佼佼者,以其简洁的API设计和高性能,提供了高度灵活的路由系统。本文通过开发一个博客系统的案例,详细介绍了FastAPI中路由的实现方法,包括基础路由定义、参数类型验证及路由分组与嵌套等,展示了如何轻松构建RESTful风格的URL映射,提升应用的可维护性和扩展性。
18 2
|
8天前
|
开发者 Java UED
大文件传输不再头疼:揭秘Struts 2如何轻松应对文件上传与下载难题!
【8月更文挑战第31天】在Web应用开发中,文件上传与下载至关重要。Struts 2作为主流Java EE框架,凭借Commons FileUpload及文件上传拦截器简化了相关操作。本文探讨Struts 2在文件传输上的优势,通过具体配置与代码示例,展示如何设置最大文件大小、使用`fileUpload`拦截器以及实现文件上传与下载功能。对于大文件传输,Struts 2不仅能够轻松应对,还支持上传进度显示,有效提升了用户体验。总体而言,Struts 2为文件传输提供了高效便捷的解决方案,助力开发者构建稳定可靠的Web应用。然而,在处理大文件时需兼顾网络带宽与服务器性能,确保传输顺畅。
25 0
|
8天前
|
API UED 开发者
Vaadin路由魔法:导航之舟,带你穿越页面迷宫!驾驭神奇URL,解锁无限可能!
【8月更文挑战第31天】Vaadin是一款现代Java Web开发框架,其路由机制结合前后端路由,确保流畅的用户体验和高效服务器资源利用。通过`@Route`注解和`Router`类,开发者可以轻松定义和管理页面路径。例如,`@Route(&quot;home&quot;)`可指定视图路径,而参数化路由如`@Route(&quot;user/:userId&quot;)`则允许URL传参。此外,Vaadin还提供了丰富的导航API和自定义路由事件监听器,助力开发者构建结构清晰且体验优秀的Web应用。
16 0
|
4月前
|
前端开发 JavaScript 应用服务中间件
前端vue2、vue3去掉url路由“ # ”号——nginx配置(二)
前端vue2、vue3去掉url路由“ # ”号——nginx配置
211 0
|
15天前
|
开发框架 .NET 开发工具
【Azure 应用服务】App Service 的.NET Version选择为.NET6,是否可以同时支持运行ASP.NET V4.8的应用呢?
【Azure 应用服务】App Service 的.NET Version选择为.NET6,是否可以同时支持运行ASP.NET V4.8的应用呢?
|
15天前
|
开发框架 监控 .NET
【Azure 应用程序见解】在Docker中运行的ASP.NET Core应用如何开启Application Insights的Profiler Trace呢?
【Azure 应用程序见解】在Docker中运行的ASP.NET Core应用如何开启Application Insights的Profiler Trace呢?
|
24天前
|
开发框架 前端开发 .NET
Asp.net Webapi 的 Post 方法不能把参数加到 URL 中?试试这样写
Asp.net Webapi 的 Post 方法不能把参数加到 URL 中?试试这样写
|
4月前
|
开发框架 搜索推荐 中间件
中间件应用路由和URL重写
【5月更文挑战第2天】中间件应用路由和URL重写
33 3
中间件应用路由和URL重写