学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(二)

路由

对于一个ASP.NET MVC应用来说,针对HTTP请求的处理实现在目标Controller类型的某个Action,每个HTTP请求不在像ASP.NET Web Forms应用一样是针对一个物理文件,而是针对某个Controller的某个Action方法。目标Controller和Action的名称由HTTP请求的URL来决定,当ASP.NET MVC接收到抵达的请求后,其首要任务就是通过当前HTTP请求解析得到目标Controller和Action的名称,这个过程是通过ASP.NET MVC的路由系统来实现的。我们通过如下几个对象构建了一个简易的路由系统。

1.RouteData

ASP.NET定义了一个全局的路由表,路由表中的每个Route对象包含一个路由模板。目标Controller和Action的名称可以通过路由变量以占位符的形式定义在模板中,也可以作为路由对象的默认值(无须出现在路由模板中)。对于每一个抵达的HTTP请求,路由系统会遍历路由表并找到一个具有与当前请求URL模式相匹配的Route对象,然后利用它解析出以Controller和Action名称为核心的路由数据。在我们自建的ASP.NET MVC框架中,通过路由解析得到的路由数据通过具有如下定义的RouteData类型表示。

public class RouteData
    {
        public IDictionary<string, object> Values { get; private set; }
        public IDictionary<string, object> DataTokens { get; private set; }
        public IRouteHandler RouteHandler { get; set; }
        public RouteBase Route { get; set; }

        public RouteData()
        {
            this.Values = new Dictionary<string, object>();
            this.DataTokens = new Dictionary<string, object>();
            this.DataTokens.Add("namespaces", new List<string>());
        }

        public string Controller
        {
            get
            {
                object controllerName = string.Empty;
                this.Values.TryGetValue("controller", out controllerName);
                return controllerName.ToString();
            }
        }

        public string ActionName
        {
            get
            {
                object actionName = string.Empty;
                this.Values.TryGetValue("action", out actionName);
                return actionName.ToString();
            }
        }
    }

RouteData定义了两个字典类型的属性Values和DataTokens,他们代表具有不同来源的路由变量,前者由对请求URL实施路由解析获得。表示Controller和Action名称的属性直接从Values属性的字典中提取,对应的Key分别为“controller”和“action”。

ASP.NET MVC的本质是由自定义的HttpModule和自定义的HttpHandler两个组件来实现的。HttpModule从RouteData对象的RouteHandler属性获得。RouteData的RouteHandler属性类型为IRouteHandler接口,该接口具有一个唯一的GetHttpHandler方法返回真正真正处理HTTP请求的HttpHandler对象。该方法有一个类型为RequestContext的参数。RequestContext表示当前(HTTP)请求的上下文,其核心就是对当前HttpContext和RouteData的封装。

public interface IRouteHandler
    {
        IHttpHandler GetHttpHandler(RequestContext requestContext);
    }
public class RequestContext
    {
        public virtual HttpContextBase HttpContext { get; set; }
        public virtual RouteData RouteData { get; set; }
    }

2.Route和RouteTable

承载路由变量的RouteData对象由路由表中与当前请求相匹配的Route对象生成,可以通过RouteData的Route属性获得这个Route对象,该属性的类型为RouteBase。如下面的代码片段所示,RouteBase是一个抽象类,他仅仅包含一个返回类型为RouteData的GetRouteData方法。

public abstract class RouteBase
    {
        public abstract RouteData GetRouteData(HttpContextBase httpContext);
    }

RouteBase的GetRouteData方法具有一个类型为HttpContextBase的参数,它代表针对当前请求的HTTP上下文。当该方法被执行的时候,它会判断自身定义的路由规则是否与当前请求相匹配,并在成功匹配的情况下实施路由解析,并将得到的路由变量封装成RouteData对象返回。如果路由规则与当前请求不匹配,则该方法直接返回Null。

我们定义了如下一个继承自RouteBase的Route类型来完成具体的路由工作。一个Route对象具有一个代表路由模板的字符串类型的Url属性。在实现的GetRouteData方法中,我们通过HttpContextBase获取当前请求的URL,如果它与路由模板的模式相匹配,则创建一个RouteData对象作为返回值。对于返回的RouteData对象,其Values属性表示的字典对对象包含直接通过解析出来的变量,而对于DataTokens字典和RouteHandler属性,则直接取自Route对象的同名属性。

 public class Route : RouteBase
    {
        public IRouteHandler RouteHandler { get; set; }
        public string Url { get; set; }
        public IDictionary<string, object> DataTokens { get; set; }

        public Route()
        {
            this.DataTokens = new Dictionary<string, object>();
            this.RouteHandler = new MvcRouteHandler();
        }

        public override RouteData GetRouteData(HttpContextBase httpContext)
        {
            IDictionary<string, object> variables;
            if (this.Match(httpContext.Request
                 .AppRelativeCurrentExecutionFilePath.Substring(2), out variables))
            {
                RouteData routeData = new RouteData();
                foreach (var item in variables)
                {
                    routeData.Values.Add(item.Key, item.Value);
                }
                foreach (var item in DataTokens)
                {
                    routeData.DataTokens.Add(item.Key, item.Value);
                }
                routeData.RouteHandler = this.RouteHandler;
                return routeData;
            }
            return null;
        }

        protected bool Match(string requestUrl,out IDictionary<string, object> variables)
        {
            variables = new Dictionary<string, object>();
            string[] strArray1 = requestUrl.Split('/');
            string[] strArray2 = this.Url.Split('/');
            if (strArray1.Length != strArray2.Length)
            {
                return false;
            }
            for (int i = 0; i < strArray2.Length; i++)
            {
                if (strArray2[i].StartsWith("{") && strArray2[i].EndsWith("}"))
                {

                    variables.Add(strArray2[i].Trim("{}".ToCharArray()), strArray1[i]);
                }
            }
            return true;
        }
    }

一个Web应用可以采用多种不同的URL模式,所以需要注册多个继承自RouteBase的Route对象,多个Route对象组成了一个路由表。在我们自定义的迷你版ASP.NET MVC框架中,路由表通过类型RouteTable表示。RouteTable仅仅具有一个类型为RouteDictionary的Routes属性表示针对整个Web应用的全局路由表。

 public class RouteTable
    {
        public static RouteDictionary Routes { get; private set; }
        static RouteTable()
        {
            Routes = new RouteDictionary();
        }
    }

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

public class RouteDictionary : Dictionary<string, RouteBase>
    {
        public RouteData GetRouteData(HttpContextBase httpContext)
        {
            foreach (var route in this.Values)
            {
                RouteData routeData = route.GetRouteData(httpContext);
                if (null != routeData)
                {
                    return routeData;
                }
            }
            return null;
        }
    }

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

public class Global : System.Web.HttpApplication
    {
        protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.Add("default", new Route { Url = "{controller}/{action}" });
        }
    }

3.UrlRoutingModule

路由表的作用是对当前的HTTP请求实施路由解析,进而得到一个以Controller和Action名称为核心的路由数据,即上面介绍的RouteData对象。整个路由解析工作是通过一个类型为UrlRoutingModule的自定义IHttpModule来完成的。

public class UrlRoutingModule : IHttpModule
    {
        public void Dispose()
        { }

        public void Init(HttpApplication context)
        {
            context.PostResolveRequestCache += OnPostResolveRequestCache;
        }
        protected virtual void OnPostResolveRequestCache(object sender, EventArgs e)
        {
            HttpContextWrapper httpContext = new HttpContextWrapper(HttpContext.Current);
            RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
            if (null == routeData)
            {
                return;
            }
            RequestContext requestContext = new RequestContext
            {
                RouteData = routeData,
                HttpContext = httpContext
            };
            IHttpHandler handler = routeData.RouteHandler.GetHttpHandler(requestContext);
            httpContext.RemapHandler(handler);
        }
    }

在实现的Init方法中,我们注册了HttpApplication的PostResolveRequestCache事件。当代表当前应用的HttpApplication对象的PostResolveRequestCache事件触发之后,UrlRoutingModule通过RouteTable的静态只读属性Routes得到表示全局路由表的RouteDictionary对象,然后根据当前HTTP上下文创建一个HttpContextWrapper对象(HttpContextWrapper是HttpContextBase的子类),并将其作为参数调用RouteDictionary对象的GetRouteData方法。

如果方法调用返回一个具体的RouteData对象,UrlRoutingModule会根据该对象本身和之前得到的HttpContextWrapper对象创建一个表示当前上下文的RequestContext对象,并将其作为参数传入RouteData的RouteHandler的GetHttpHandler方法得到一个HttpHandler对象。UrlRoutingModule最后调用HttpContextWrapper对象的RemapHandler方法对得到的HttpHandler对象进行映射,那么针对当前HTTP请求的后续处理将由这个HttpHandler来接手。

下一章我们讲解Controller的激活。

时间: 2024-12-04 13:27:02

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(二)的相关文章

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(一)

ASP.NET MVC是如何运行的 ASP.NET由于采用了管道式设计,所以具有很好的扩展性,整个ASP.NET MVC应用框架就是通过扩展ASP.NET实现的.通过上面对ASP.NET管道设计的介绍我们知道,ASP.NET 的扩展点主要体现在HttpModule和HttpHandler这两个核心组件之上,整个ASP.NET MVC框架就是通过自定义的HttpModule和HttpHandler建立起来的. 接下来我们通过自定义组件来模拟ASP.NET MVC的运行原理. 1.4.1建立在迷你版

学习ASP.NET MVC5框架揭秘笔记-ASP.NET路由(二)

实例演示:通过路由实现请求地址与.aspx页面的映射 我们创建一个简单的ASP.NET Web Forms应用,并采用一套独立于.aspx文件路径的URL来访问对应的Web页面,两者之间的映射通过路由来实现,我们依然沿用员工管理的场景. 首先我们将员工的所有信息(ID.姓名.性别.出生日期和所在部门)定义在如下所示的Employee类型中,然后定义一个EmployeeRepository类型来维护员工列表的数据.简单起见,员工列表通过静态字段employees表示.EmployeeReposit

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(四)

Action的执行 作为Controller的基类ControllerBase,它的Execute方法主要作用在于执行目标Action方法.如果目标Action方法返回一个ActionResult对象,它还需要执行该对象来对当前请求予以响应.在ASP.NET MVC框架中,两者的执行是通过一个叫做ActionInvoker的对象来完成的. 1.ActionInvoker 我们同样为ActionInvoker定义了一个借口IActionInvoker.如下所示.该接口定义了唯一的方法InvokeA

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(五)

2.AreaRegistration的缓存 Area的注册(主要是基于Area的路由映射注册)通过具体的AreaRegistration来完成.在应用启动的时候,ASP.NET MVC会遍历通过调用BuildManager的静态方法GetReferencedAssemblies得到的程序集列表,并从中找到所有AreaRegistration类型.如果一个应用涉及太多的程序集,则这个过程可能会耗费很多时间.为了提高性能,ASP.NET MVC会对解析出来的所有AreaRegistration类型列

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(三)

Controller的激活 ASP.NET MVC的路由系统通过注册的路由表对当前HTTP请求实施路由解析,从而得到一个用于封装路由数据的RouteData对象,这个过程是通过自定义的UrlRoutingModule对HttpApplication的PostResolveRequestCache事件进行注册实现的.由于得到的RouteData对象中已经包含了目标Controller的名称,我们需要根据该名称激活对应的Controller对象. 1.MvcRouteHandler 通过前面的介绍我

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC是如何运行的(五)

完整的流程 对于我们创建的这个迷你版的ASP.NET MVC框架来说,虽然很多细节被直接忽略掉,但是它基本上能够展现整个ASP.NET MVC框架的全貌,支持这个开发框架的核心对象可以说一个不少.接下来我们对通过这个模拟框架展现出来的ASP.NET MVC针对请求的处理流程作一个简单的概括. 由于UrlRoutingModule这个HttpModule被注册到Web应用中,所以对于每个抵达的请求来说,当代表当前应用的HttpApplication对象的PostResolveRequestCach

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(二)

2.2.2 路由注册 ASP.NET MVC通过调用代表全局路由表的RouteCollection对象的扩展方法MapRoute进行路由注册.我们来进行一个简单的实例演示.我们依然沿用之前关于获取天气信息的路由模板,看看通过这种方式注册的Route对象针对匹配的请求将返回怎样一个RouteData对象. 我们创建一个空的ASP.NET Web程序,并手动添加"System.Web.Mvc.dll"和"System.Web.WebPages.Razor.dll"的引用

学习ASP.NET MVC5框架揭秘笔记-ASP.NET MVC路由(三)

2.2.3缺省URL参数 当通过VisualStudio的ASP.NET MVC项目模板创建一个Web应用后,它会为我们注册如下一个模板为"{controller}/{action}/{id}"的默认Route对象.3个路由模板均有相应额默认值.但是变量名为id的默认值为URLParameter.Optional.按照字面的意思,我们将其称为可缺省URL参数.那么将路由变量的默认值进行如此设置与设置一个具体的默认值有什么区别呢? routes.MapRoute( name: "

学习ASP.NET MVC5框架揭秘笔记-ASP.NET路由(六)

4.Route RouteBase是一个抽象类,在ASP.NET路由系统的应用编程接口中,Route类型是其唯一的直接继承者,在默认的情况下调用RouteCollection的MapPageRoute方法在路由表中添加的就是这么一个对象.如下面的代码片段所示,Route类型具有一个字符串类型的属性Url,它代表绑定在路由对象上的路由模板. public class Route : RouteBase { public Route(string url,IRouteHandler routeHan