ASP.NET MVC的路由系统通过对HTTP请求的解析得到表示Controller、Action和其他相关的数据,并以此为依据激活Controller对象,调用相应的Action方法,并将方法返回的ActionResult写入HTTP回复中。为了更好的演示其实现原理,我创建一个简单的ASP.NET Web应用来模拟ASP.NET MVC的路由机制。这个例子中的相关组件基本上就是根据ASP.NET MVC的同名组件设计的,只是我将它们进行了最大限度的简化,因为我们只需要用它来演示大致的实现原理而已。[源代码从这里下载]

目录:
一、一个通过查询字符串表示Controller和Action的“MVC”程序
二、通过Route解析HTTP请求获得路由信息
三、在Global.asax中注册Route
四、Route的执行
五、通过MvcHandler处理请求
六、将ActionResult写入Http回复
七、实例的配置和定义

一、一个通过查询字符串表示Controller和Action的“MVC”程序

如右图所示,我们的Web应用非常简单。HomeController.cs为定义Controller类型的文件,而Index.html表示HomeController中名称为Index的Action对应的View。我们按照ASP.NET MVC的原理,通过解析请求URL得到Controller和Action的名称。如果Controller为Home,则激活HomeController,如果当前的Action为Index,则将Index.html这个静态文件的内容作为HTTP回复返回。

我不想定义复杂的解析Controller和Action的逻辑,再这里我直接通过请求URL相应的查询字符串controler和action表示Controller和Action的名称。也就是说如果通过浏览器访问地址http://localhost/mvcapp/?controller=Home&action=Index 可以访问到Index.html中的内容(注:我们并没有将Index.html作为站点的默认页面)。

接下来我简单的介绍一下是哪些组建促使这个简单的ASP.NET Web应用能够按照MVC的模式来执行。为了使你能够在真正的ASP.NET MVC找到匹配的组件,我们采用了相同的接口和类型名称。

二、通过Route解析HTTP请求获得路由信息

我定义了如下一个RouteData类型表示解析HTTP请求得到的Controller和Action等信息。Assemblies和Namespaces表示需要引入的命名空间和程序集,这是因为URL中只能解析出Controller的类型名称,需要相应的命名空间采用得到它的类型全名。如果对应的程序集不曾加载,还需要加载相应的程序集。

   1: public class RouteData

   2: {

   3:     public string Controller { get; set; }

   4:     public string Action { get; set; }

   5:     public IList<string> Assemblies { get; private set; }

   6:     public IList<string> Namespaces { get; private set; }

   7:     public IRouteHandler RouteHandler { get; set; }

   8:  

   9:     public RouteData(string controller, string action, IRouteHandler routeHandler)

  10:     {

  11:         this.Controller = controller;

  12:         this.Action = action;

  13:         this.RouteHandler = routeHandler;

  14:         this.Namespaces = RouteTable.Namespaces;

  15:         this.Assemblies = RouteTable.Assemblies;

  16:     }

  17: }

真正实现对HTTP请求进行解析并得到RouteData的Route继承自基类RouteBase。我们还定义个了一个表示Route集合的RouteCollection类型,它的GetRouteData方法对集合的所有Route对象进行遍历,并调用其GetRouteData方法。如果得到的RouteData不为空,则返回之。

   1: public abstract class RouteBase

   2: {

   3:     public abstract RouteData GetRouteData(HttpContextBase httpContext);

   4: }

   5:  

   6: public class RouteCollection: Collection<RouteBase>

   7: {

   8:     public RouteData GetRouteData(HttpContextBase httpContext)

   9:     {

  10:         foreach (RouteBase route in this)

  11:         {

  12:             var routeData = route.GetRouteData(httpContext);

  13:             if (null != routeData)

  14:             {

  15:                 return routeData;

  16:             }

  17:         }

  18:         return null;

  19:     }

  20: }

和ASP.NET MVC一样,我们定义了如下一个RouteTable对象,其静态属性正是一个RouteCollection对象。两个静态属性Namespaces和Assemblies为命名空间和程序集名称的全局维护。

   1: public class RouteTable

   2: {

   3:     static RouteTable()

   4:     {

   5:         Routes = new RouteCollection();

   6:         Namespaces = new List<string>();

   7:         Assemblies = new List<string>();

   8:     }

   9:     public static RouteCollection Routes { get; private set; }

  10:     public static IList<string> Namespaces { get; private set; }

  11:     public static IList<string> Assemblies { get; private set; }

  12: }

而我们实例中完成基于查询字符串的Controller和Action解析的QueryStringRoute对应如下。在GetRouteData方法中,除了根据查询字符解析并初始化Controller和Action名称之外,还将RouteHandler指定为MvcRouteHandler。而MvcRouteHandler得GetHttpHandler方法直接返回的是根据RequestContext创建的MvcHandler对象。

   1: public class QueryStringRoute : RouteBase

   2: {

   3:     public override RouteData GetRouteData(HttpContextBase httpContext)

   4:     {

   5:         if (httpContext.Request.QueryString.AllKeys.Contains("controller") &&

   6:             httpContext.Request.QueryString.AllKeys.Contains("controller") )

   7:         {

   8:             string controller = httpContext.Request.QueryString["controller"];

   9:             string action = httpContext.Request.QueryString["action"];

  10:             IRouteHandler routeHandler = new MvcRouteHandler();

  11:             return new RouteData(controller, action, routeHandler);               

  12:         }

  13:         return null;

  14:     }

  15: }

  16:  

  17: public class MvcRouteHandler: IRouteHandler

  18: {

  19:     public IHttpHandler GetHttpHandler(RequestContext requestContext)

  20:     {

  21:         return new MvcHandler(requestContext);

  22:     }

  23: }

三、在Global.asax中注册Route

通过上面定义的RouteTable类型,我们在Global.asax中按照如下的方式在应用启动的时候QueryStringRoute对象添加到RouteTable的静态属性Routes表示的Route列表中。同时为需要的命名空间和程序集名称进行初始化,以辅助后续步骤中对Controller的创建。

   1: public class Global : System.Web.HttpApplication

   2: {

   3:     protected void Application_Start(object sender, EventArgs e)

   4:     {

   5:         RouteTable.Routes.Add(new QueryStringRoute());

   6:         RouteTable.Assemblies.Add("MvcApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");

   7:         RouteTable.Namespaces.Add("Artech.MvcApp");

   8:     }

   9: }

四、Route的执行

通过RouteTable的Routes属性表示的Route列表对请求的解析和路由信息的获取是通过自定义的HttpModule来实现的,它的类型为UrlRoutingModule。如下面的代码片断所示,UrlRoutingModule注册了HttpApplication的PostResolveRequestCache事件,并在该事件触发的时候调用Route列表的GetRouteData方法,并根据得到RouteData创建RequestContext。最后通过RouteData的RouteHandler得到真正用于处理该请求的HttpHandler对象,并对其进行映射。这意味着后续将会采用这个映射的HttpHandler进行请求的处理。

   1: public class UrlRoutingModule: IHttpModule

   2: {

   3:     public void Dispose() { }

   4:     public void Init(HttpApplication context)

   5:     {

   6:         context.PostResolveRequestCache += (sender, args) =>

   7:             {

   8:                 HttpContextWrapper contextWrapper = new HttpContextWrapper(context.Context);

   9:                 HttpContextBase httpContext = (HttpContextBase)contextWrapper;

  10:                 RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);

  11:                 if (null == routeData)

  12:                 {

  13:                     return;

  14:                 }

  15:                 RequestContext requestContext = new RequestContext { HttpContext = httpContext, RouteData = routeData };                    

  16:                 httpContext.RemapHandler(routeData.RouteHandler.GetHttpHandler(requestContext));

  17:             };

  18:     }

  19: }

五、通过MvcHandler处理请求

在UrlRoutingModule映射的实际上是具有如下定义的MvcHandler,它具有一个RequestContext属性通过构造函数进行初始化。在ASP.NET MVC中,真正的请求处理体现在根据路由信息创建Controller,并执行相应的Action方法。这两个步骤体现的ProcessRequest方法中。

   1: public class MvcHandler: IHttpHandler

   2: {

   3:     public RequestContext RequestContext{get; private set;}

   4:     public IControllerFactory ControllerFactory

   5:     {

   6:         get { return ControllerBuilder.Current.GetControllerFactory(); }

   7:     }

   8:     public MvcHandler(RequestContext requestContext)

   9:     {

  10:         this.RequestContext = requestContext;

  11:     }

  12:     public bool IsReusable

  13:     {

  14:         get { return false; }

  15:     }

  16:     public void ProcessRequest(HttpContext context)

  17:     {

  18:         RouteData routeData = this.RequestContext.RouteData;

  19:         var controller =  this.ControllerFactory.CreateController(this.RequestContext, routeData.Controller);

  20:         controller.Execute(this.RequestContext);

  21:     }

  22: }

Controller实现了具有如下定义的接口IController,所有Action方法都通过Execute方法执行,该方法的参数的表示当前请求上下文的RequestContext对象。IController通过相应的Controller工厂创建,下面的代码同时也定义了Controller工厂接口的定义。

   1: public interface IController

   2: {

   3:     void Execute(RequestContext requestContext);

   4: }

   5: public interface IControllerFactory

   6: {

   7:     IController CreateController(RequestContext requestContext, string controllerName);

   8: }

我们定义了如下一个简单名称为DefaultController,它的Execute方法定义很简单:通过包含在RequestContext的RouteData得到当前的Action,并将它作为方法名得到相应的MethodInfo对象,滨个通过反射调用它得到一个ActionResult对象,最后执行ActionResult的ExecuteResult方法。该方法的参数是基于RequestContext创建的另一个上下文ControllerContext。

   1: public class DefaultController : IController

   2: {

   3:     public void Execute(RequestContext requestContext)

   4:     {

   5:         string action = requestContext.RouteData.Action;

   6:         MethodInfo method = this.GetType().GetMethod(action);

   7:         ActionResult result = (ActionResult)method.Invoke(this, null);

   8:         ControllerContext controllerContext = new ControllerContext

   9:         {

  10:             RequestContext = requestContext

  11:         };

  12:         result.ExecuteResult(controllerContext);

  13:     }

  14: }

我们定义了具有如下定义的Controller工厂类DefaultControllerFactory。创建Controller的逻辑也不复杂:通过RouteData表示的Controller名称得到相应的Controller类型,通过反射创建Controller对象。由于RouteData中只包含Controller的名称,所以需要通过命名空间和程序集的辅助才能解析出真正的类型。

   1: class DefaultControllerFactory : IControllerFactory

   2: {

   3:     public IController CreateController(RequestContext requestContext, string controllerName)

   4:     {

   5:         RouteData routeData = requestContext.RouteData;

   6:         string controllerType = string.Format("{0}Controller", controllerName);

   7:         IController controller;

   8:         controller = this.CreateControler(controllerType);

   9:         if (null != controller)

  10:         {

  11:             return controller;

  12:         }

  13:         foreach (string assembly in routeData.Assemblies)

  14:         {

  15:             controller = this.CreateControler(controllerType, assembly);

  16:             if (null != controller)

  17:             {

  18:                 return controller;

  19:             }

  20:  

  21:             foreach (string ns in routeData.Namespaces)

  22:             {

  23:                 controllerType = string.Format("{0}.{1}Controller", ns, controllerName);

  24:                 controller = this.CreateControler(controllerType, assembly);

  25:                 if (null != controller)

  26:                 {

  27:                     return controller;

  28:                 }

  29:             }

  30:         }

  31:  

  32:         throw new InvalidOperationException("Cannot locate the controller");

  33:     }

  34:     private IController CreateControler(string controllerType, string assembly = null)

  35:     {

  36:         Type type = null;

  37:         if (null == assembly)

  38:         {

  39:             type = Type.GetType(controllerType);

  40:         }

  41:         else

  42:         {

  43:             type = Assembly.Load(assembly).GetType(controllerType);

  44:         }

  45:         if (null == type)

  46:         {

  47:             return null;

  48:         }

  49:         return Activator.CreateInstance(type) as IController;

  50:     }

  51: }

六、将ActionResult写入Http回复

Controller的Action方法的返回值为具有如下定义的ActionResult类型,通过ExecuteResult方法将相应的执行结果写入HTTP回复中。我定义了如下一个StaticViewResult,它根据RouteData中的Action信息找到匹配的.html静态文件,并将文件的内容写入HttpResponse。

   1: public abstract class ActionResult

   2: {

   3:     public abstract void ExecuteResult(ControllerContext context);

   4: }

   5:  

   6: public class StaticViewResult: ActionResult

   7: {

   8:     public override void ExecuteResult(ControllerContext context)

   9:     {

  10:         context.RequestContext.HttpContext.Response.WriteFile(context.RequestContext.RouteData.Action + ".html");

  11:     }

  12: }

七、实例的配置和定义

在我们的实例中定义的HomeController定义如下,在表示Action的Index方法中,直接返回一个StaticViewResult对象。

   1: public class HomeController : DefaultController

   2: {

   3:     public ActionResult Index()

   4:     {

   5:         return new StaticViewResult();

   6:     }

   7: }

然后在配置中进行了针对UrlRoutingModule的注册,仅此而已。

   1: <configuration>

   2:   <system.webServer>

   3:     <modules>

   4:       <add name="UrlRoutingModule" type="Artech.MvcRouting.UrlRoutingModule, Artech.MvcRouting"/>

   5:     </modules>

   6:   </system.webServer>

   7: </configuration>

通过一个模拟程序让你明白ASP.NET MVC是如何运行的的更多相关文章

  1. 通过一个模拟程序让你明白WCF大致的执行流程

    原文http://www.cnblogs.com/artech/archive/2011/12/07/wcf-how-to-work.html 在<通过一个模拟程序让你明白ASP.NET MVC ...

  2. 仅此一文让你明白ASP.NET MVC 之View的显示(仅此一文系列二)

    题外话 一周之前写的<仅此一文让你明白ASP.NET MVC原理>受到了广大学习ASP.NET MVC同学的欢迎,于是下定决心准备把它写成一个系列,以满足更多求知若渴的同学们.蒋金楠老师已 ...

  3. 仅此一文让你明白ASP.NET MVC 之Model的呈现(仅此一文系列三)

    本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class HomeController : Controller { pu ...

  4. 仅此一文让你明白ASP.NET MVC 之Model的呈现

    本文目的 我们来看一个小例子,在一个ASP.NET MVC项目中创建一个控制器Home,只有一个Index: public class HomeController : Controller { pu ...

  5. 仅此一文让你明白ASP.NET MVC原理

    ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义HttpModule,用来解析Controller与Action名称: 一个名为MvcHandler ...

  6. 【转】仅此一文让你明白ASP.NET MVC原理

    原文地址:http://www.cnblogs.com/DotCpp/p/3269043.html ASP.NET MVC由以下两个核心组成部分构成: 一个名为UrlRoutingModule的自定义 ...

  7. ASP.NET没有魔法——ASP.NET MVC是如何运行的?它的生命周期是什么?

    前面的文章我们使用ASP.NET MVC创建了个博客应用,那么它是如何工作的呢?我们都知道ASP.NET的程序需要部署到IIS上才能够通过浏览器来访问,那么IIS与ASP.NET MVC程序之间又是如 ...

  8. ASP.NET开发实战——(四)ASP.NET MVC是如何运行的?它的生命周期是什么?

    前面的文章我们使用ASP.NET MVC创建了个博客应用,那么它是如何工作的呢?我们都知道ASP.NET的程序需要部署到IIS上才能够通过浏览器来访问,那么IIS与ASP.NET MVC程序之间又是如 ...

  9. 仅此一文让你明白ASP.NET MVC 之View的显示

    有些人要问题,为什么我要学框架?这里我简单说一下,深入理解一个框架,给你带来最直接的好处: 使用框架时,遇到问题可以快速定位,并知道如何解决: 当框架中有些功能用着不爽时,你可以自由扩展,实现你想要的 ...

随机推荐

  1. wamp环境 安装memcache 扩展

    这两天在研究tp的memcached缓存 总是遇到坑 在网上找了很多教程看终于弄出来了现在拿出来分享 首先安装memcached下载memcache压缩包 使用cmd以管理员命令去安装 E:\wamp ...

  2. CSS篇之动画(2)

    animation-name(自定义动画) name为动画名称.不要用中文,尽量用与动画相关的名称.元素所应用的动画名称,必须与规则@keyframes配合使用,因为动画名称由@keyframes定义 ...

  3. (一)Linux相关内容的简介

    1.Linux内核官网:www.kernel.org 2.Linux主要发行版本: (1)redhat  服务器中常见(部分功能收费); (2)Ubuntu  图形界面较好,但是服务器中很少用: (3 ...

  4. 细数iOS上的那些安全防护

    细数iOS上的那些安全防护  龙磊,黑雪,蒸米 @阿里巴巴移动安全 0x00 序 随着苹果对iOS系统多年的研发,iOS上的安全防护机制也是越来越多,越来越复杂.这对于刚接触iOS安全的研究人员来说非 ...

  5. 如何让用户只能访问特定的数据库(MSSQL)

    背景 客户的SQL Server实例上有多个厂商的数据库,每个数据库由各自的进行厂进行商维护, 为了限定不同厂商的维护人员只能访问自己的数据库,现需要给各个厂商限定权限,让他们登录SQL Server ...

  6. SQL Server 数据库设计规范

    数据库设计规范 1.简介 数据库设计是指对一个给定的应用环境,构造最优的数据库模式,建立数据库及其他应用系统,使之能有效地存储数据,满足各种用户的需求.数据库设计过程中命名规范很是重要,命名规范合理的 ...

  7. Pointer's NULL And 0

    问题起源 在使用Qt框架的时候, 经常发现一些构造函数 *parent = 0 这样的代码. 时间长了, 就觉的疑惑了. 一个指针不是等于NULL吗? 这样写, 行得通吗? 自己测试一下就可以了. 测 ...

  8. CentOS On VirtualBox

    背景 后台开发需要随时与服务器交互,本人使用Mac开发.但是不愿意在Mac上直接安装redis以及mysql等等工具.所以选择在VirtualenvBox下安装一个服务器系统,并且使用ssh与其连接. ...

  9. ionic中关于ionicView 的生命周期

    当我们来回切换页面时候,视图被缓存下来,不用每次再去new一个新的视图,可以大大地提高性能.当跳出一个视图后,视图的元素被保存在DOM中,它的作用域也就不在$watch的作用域内,当我们访问一个已经被 ...

  10. eCharts动态加载各省份的数据

    假如从数据库读出以下数据,如何将数据展示在地图之上 1.部门的名称数据: List deptname=[联通事业部-上海联通项目组, 联通事业部-河南联通项目组, 联通事业部-贵州联通项目组, 联通事 ...