天天看點

借助Web模拟程式 了解ASP.NET MVC運作

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回複傳回。

  

  我不想定義複雜的解析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  

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  

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  

3:     public override RouteData GetRouteData(HttpContextBase httpContext)  

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  

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

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");  

9: }

Route的執行

  通過RouteTable的Routes屬性表示的Route清單對請求的解析和路由資訊的擷取是通過自定義的HttpModule來實作的,它的類型為UrlRoutingModule。如下面的代碼片斷所示,UrlRoutingModule注冊了HttpApplication的PostResolveRequestCache事件,并在該事件觸發的時候調用Route清單的GetRouteData方法,并根據得到RouteData建立RequestContext。最後通過RouteData的RouteHandler得到真正用于處理該請求的HttpHandler對象,并對其進行映射。這意味着後續将會采用這個映射的HttpHandler進行請求的處理。

1: public class UrlRoutingModule: IHttpModule  

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  

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

4:     public IControllerFactory ControllerFactory  

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

7:     }  

8:     public MvcHandler(RequestContext requestContext)  

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  

3:     void Execute(RequestContext requestContext);  

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  

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  

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

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;  

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  

3:     public abstract void ExecuteResult(ControllerContext context);  

6: public class StaticViewResult: ActionResult  

8:     public override void ExecuteResult(ControllerContext context)  

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

1: public class HomeController : DefaultController  

3:     public ActionResult Index()  

5:         return new StaticViewResult();  

6:     }  

7: }

1: <configuration>  

2:   <system.webServer>  

3:     <modules>  

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

5:     </modules>  

6:   </system.webServer>  

7: </configuration>