技术开发 频道

借助Web模拟程序 了解ASP.NET MVC运行

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

        一个通过查询字符串表示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回复返回。

借助模拟程序  了解ASP.NET MVC运行

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

借助模拟程序  了解ASP.NET MVC运行

  接下来我简单的介绍一下是哪些组建促使这个简单的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: }
0
相关文章