自己动手写一个简单的MVC框架(第二版)
一、ASP.NET MVC核心機(jī)制回顧
在ASP.NET MVC中,最核心的當(dāng)屬“路由系統(tǒng)”,而路由系統(tǒng)的核心則源于一個(gè)強(qiáng)大的System.Web.Routing.dll組件。
在這個(gè)System.Web.Routing.dll中,有一個(gè)最重要的類叫做UrlRoutingModule,它是一個(gè)實(shí)現(xiàn)了IHttpModule接口的類,在請(qǐng)求處理管道中專門針對(duì)ASP.NET MVC請(qǐng)求進(jìn)行處理。首先,我們要了解一下UrlRoutingModule是如何起作用的。
(1)IIS網(wǎng)站的配置可以分為兩個(gè)塊:全局 Web.config 和本站 Web.config。Asp.Net Routing屬于全局性的,所以它配置在全局Web.Config 中,我們可以在如下路徑中找到:“$\Windows\Microsoft.NET\Framework\版本號(hào)\Config\Web.config“
<?xml version="1.0" encoding="utf-8"?><!-- the root web configuration file --><configuration><system.web><httpModules><add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /></httpModules></system.web></configuration>(2)通過在全局Web.Config中注冊(cè)?System.Web.Routing.UrlRoutingModule,IIS請(qǐng)求處理管道接到請(qǐng)求后,就會(huì)加載 UrlRoutingModule類型的Init()方法。
PS : 在UrlRoutingModule中為請(qǐng)求處理管道中的第七個(gè)事件PostResolveRequestCache注冊(cè)了一個(gè)事件處理方法:OnApplicationPostResolveRequestCache。從這里可以看出:ASP.NET MVC的入口在UrlRoutingModule,即訂閱了HttpApplication的第7個(gè)管道事件PostResolveRequestCahce。換句話說,是在HtttpApplication的第7個(gè)管道事件處對(duì)請(qǐng)求進(jìn)行了攔截。
現(xiàn)在我們將ASP.NET MVC的請(qǐng)求處理分為兩個(gè)重要階段來看看:
①在第七個(gè)事件中創(chuàng)建實(shí)現(xiàn)了IHttpHandler接口的MvcHandler
當(dāng)請(qǐng)求到達(dá)UrlRoutingModule的時(shí)候,UrlRoutingModule取出請(qǐng)求中的Controller、Action等RouteData信息,與路由表中的所有規(guī)則進(jìn)行匹配,若匹配,把請(qǐng)求交給IRouteHandler,即MVCRouteHandler。我們可以看下UrlRoutingModule的源碼來看看,以下是幾句核心的代碼:
View Code從源碼片段中可以看出,最后將請(qǐng)求轉(zhuǎn)移給了實(shí)現(xiàn)了IHttpHandler接口的處理程序進(jìn)行后續(xù)的處理。在ASP.NET MVC的實(shí)現(xiàn)中,是將請(qǐng)求交給了MvcHandler這個(gè)類,通過執(zhí)行其ProcessRequest方法來進(jìn)行后續(xù)的處理。
②在第十一個(gè)事件與第十二個(gè)事件之間調(diào)用MvcHandler的ProcessRequest()方法
(1)在WebForm中,此階段會(huì)調(diào)用Page類對(duì)象的ProcessRequest()方法。在ASP.NET MVC中,會(huì)調(diào)用MvcHandler的ProcessRequest()方法,此方法會(huì)激活具體請(qǐng)求的Controller類對(duì)象,觸發(fā)Action方法,返回ActionResult實(shí)例。
(2)如果ActionResult是非ViewResult,比如JsonResult, ContentResult,這些內(nèi)容將直接被輸送到Response響應(yīng)流中,顯示給客戶端;如果是ViewResult,就會(huì)進(jìn)入下一個(gè)渲染視圖環(huán)節(jié)。
(3)在渲染視圖環(huán)節(jié),ViewEngine找到需要被渲染的視圖,View被加載成WebViewPage<TModel>類型,并渲染生成Html,最終返回Html。
二、我的MVC框架核心部分介紹
2.1 解決方案概覽
在該解決方案中,一共有兩個(gè)項(xiàng)目:
一個(gè)是App,它是一個(gè)由最小化的引用環(huán)境(只引用了System和System.Web,以及Mvc.Lib)搭建起來的一個(gè)Web應(yīng)用項(xiàng)目,借助MVC核心類庫(kù)(Mvc.Lib)實(shí)現(xiàn)了MVC模式。
一個(gè)是Lib,它是一個(gè)模擬ASP.NET MVC框架的最小化、輕量級(jí)的迷你MVC框架,其中Mvc文件夾模擬System.Web.Mvc,Routing文件夾模擬System.Web.Routing,而View則簡(jiǎn)單地借助NVelocity模板引擎提供View視圖服務(wù)。
2.2 MVC核心類庫(kù)
(1)Routing
從第一部分我們可以知道,ASP.NET MVC的入口在于UrlRoutingModule,因此這里我們便模擬實(shí)現(xiàn)了一個(gè)UrlRoutingModule.
/// <summary>/// 解析請(qǐng)求中的路由數(shù)據(jù),并分發(fā)請(qǐng)求到Handler/// </summary>public class UrlRoutingModule : IHttpModule{public void Init(HttpApplication application){// 注冊(cè)ASP.NET請(qǐng)求處理管道的第七個(gè)事件application.PostResolveRequestCache += Application_PostResolveRequestCache;}// 假設(shè)請(qǐng)求 http://www.edisonchou.cn/home/indexprivate void Application_PostResolveRequestCache(object sender, EventArgs e){var application = sender as HttpApplication;var context = application.Context;// 根據(jù)全局路由表解析當(dāng)前請(qǐng)求的路徑var requestUrl = context.Request.AppRelativeCurrentExecutionFilePath.Substring(2);// 遍歷全局路由表中的路由規(guī)則解析數(shù)據(jù)IDictionary<string, object> routeData;var route = RouteTable.MatchRoutes(requestUrl, out routeData);if (route == null){// 404 Not Foundthrow new HttpException(404, "Not Found!");}// 獲取處理請(qǐng)求的Handler處理程序if (!routeData.ContainsKey("controller")){// 404 Not Foundthrow new HttpException(404, "Not Found!");}var handler = route.GetRouteHandler(routeData);// 為當(dāng)前請(qǐng)求指定Handler處理程序 context.RemapHandler(handler);}public void Dispose(){this.Dispose();}}該UrlRoutingModule通過注冊(cè)ASP.NET請(qǐng)求處理管道的第七個(gè)事件,來實(shí)現(xiàn)對(duì)URL地址進(jìn)行路由規(guī)則的處理,并將最后生成的路由數(shù)據(jù)交給MvcHandler進(jìn)行后續(xù)處理。這里我省略了ASP.NET MVC源碼中MvcRouteHandler生成MvcHandler的步驟,直接丟給MvcHandler處理。
核心部分有兩點(diǎn),一是路由規(guī)則的匹配,二是為請(qǐng)求指定handler。
在路由規(guī)則的匹配中,通過設(shè)置路由數(shù)據(jù)鍵值對(duì)(Dictionary),并將設(shè)置好的路有數(shù)據(jù)傳遞給MvcHandler。具體的流程如下圖所示,這里就不再展示源碼,請(qǐng)自行下載DEMO查看:
(2)Mvc
在此文件夾中,實(shí)現(xiàn)了三個(gè)核心的部分:
① 最核心的處理者 : MvcHandler
public class MvcHandler : IHttpHandler{private IDictionary<string, object> routeData;public MvcHandler(IDictionary<string, object> routeData){this.routeData = routeData;}public void ProcessRequest(HttpContext context){var controllerName = routeData["controller"].ToString();// 借助控制器工廠創(chuàng)建具體控制器實(shí)例IController controller = DefaultControllerFactory.CreateController(controllerName);// 確保有找到一個(gè)Controller處理請(qǐng)求if (controller == null){// 404 Not Found!throw new HttpException(404, "Not Found");}// 封裝請(qǐng)求var requestContext = new RequestContext { HttpContext = context, RouteData = routeData };// 開始執(zhí)行var result = controller.Execute(requestContext);result.Execute(requestContext);}public bool IsReusable{get{return false;}}}在MvcHandler類中,主要經(jīng)歷了以下事件:
② 花樣的返回類型 : ActionResult 以及它的子類們
在以往的ASP.NET MVC開發(fā)中,我們?cè)贏ction方法的編寫中,總會(huì)看到它們的返回類型都是以ActionResult為基類的各種Result類型。
/// <summary>/// Action統(tǒng)一的返回類型/// </summary>public abstract class ActionResult{public abstract void Execute(RequestContext context);}因此,這里也實(shí)現(xiàn)了ActionResult這個(gè)抽象類,并以此為基礎(chǔ)實(shí)現(xiàn)了ContentResult、JsonResult以及ViewResult。它們的區(qū)別就在于是不同的返回類型,因此有不同的處理。
這里以ContentResult 和 JsonResult 為例,來看看具體做了什么處理。
[ContentResult]
public class ContentResult : ActionResult{private string content;private string contentType;public ContentResult(string content, string contentType){this.content = content;this.contentType = contentType;}public override void Execute(RequestContext context){context.HttpContext.Response.Write(content);context.HttpContext.Response.ContentType = contentType;}}[JsonResult]
public class JsonResult : ActionResult{private object paraObj;public JsonResult(object paraObj){this.paraObj = paraObj;}public override void Execute(RequestContext context){JavaScriptSerializer jss = new JavaScriptSerializer();var json = jss.Serialize(paraObj);context.HttpContext.Response.Write(json);context.HttpContext.Response.ContentType = "application/json";}}相信有經(jīng)驗(yàn)的讀者一眼就看穿了,因此這里也就不再多說了。
③ 路由的擴(kuò)展者 : RouteExtend
在以往的ASP.NET MVC開發(fā)中,我們會(huì)在Global全局應(yīng)用處理文件中為項(xiàng)目注冊(cè)路由規(guī)則,但卻不知道其實(shí)我們常用的MapRoute方法其實(shí)是一個(gè)擴(kuò)展方法,它并不位于System.Web.Routing這個(gè)類庫(kù)之中,而是位于System.Web.Mvc這個(gè)類庫(kù)之中。
因此,我們也在Mvc文件夾中實(shí)現(xiàn)了一個(gè)RouteExtend類,它為RouteTable類的Route集合實(shí)現(xiàn)了一個(gè)擴(kuò)展方法:
/// <summary>/// Route 的擴(kuò)展方法所在類/// </summary>public static class RouteExtend{/// <summary>/// 指定MvcHandler來處理/// </summary>public static void MapRoute(this IList<Route> source, string urlTemplate, object defaults){MapRoute(source, urlTemplate, defaults, routeData => new MvcHandler(routeData));}/// <summary>/// 通過指定實(shí)現(xiàn)了IHttpHandler的處理程序來處理/// </summary>public static void MapRoute(this IList<Route> source, string urlTemplate, object defaults, Func<IDictionary<string, object>, IHttpHandler> handler){source.Add(new Route(urlTemplate, defaults, handler));}}可以看出,MvcHandler是在這里傳入的(Mvc與Routing是單向依賴)。那么,為什么還要提供一個(gè)可傳入自定義Handler的接口呢?因?yàn)?#xff0c;不同的路由規(guī)則有可能需要不同的實(shí)現(xiàn)IHttpHandler的處理程序來處理,也不一定就非得是MvcHandler。
(3)View
在ASP.NET MVC中提供了aspx與Razor等模板引擎,這里我偷了懶,直接借助了NVelocity模板引擎來實(shí)現(xiàn)。因此,這個(gè)文件夾中只有一個(gè)VelocityHelper類(我直接從網(wǎng)上搜索的),該類可以幫助我們找到指定的HTML并綁定Model實(shí)體。
View Code三、我的MVC框架應(yīng)用實(shí)例
3.1 MVC 應(yīng)用DEMO介紹
這是一個(gè)ASP.NET 空Web應(yīng)用項(xiàng)目搭建起來的MVC Web應(yīng)用項(xiàng)目,它移除了自帶的所有引用項(xiàng)目,僅僅保留了System和System.Web,做到了盡可能地“純凈”。通過引入Mvc.Lib核心類庫(kù),建立Controller、Model和View文件夾以及對(duì)應(yīng)的類和HTML來實(shí)現(xiàn)MVC模式。
(1)引入Mvc.Lib核心類庫(kù)之后,需要配置一下Web.config,使UrlRoutingModule能夠正常工作:
<system.web><compilation debug="true" targetFramework="4.5"/><httpRuntime targetFramework="4.5"/><!-- HttpModule配置(IIS6版本) --><httpModules><add name="UrlRoutingModule" type="Manulife.Web.Mvc.Lib.Routing.UrlRoutingModule"/></httpModules></system.web><system.webServer><!-- 配置不去校驗(yàn)是否是集成模式 --><validation validateIntegratedModeConfiguration="false"/><!-- HttpModule配置(IIS7及以上版本) --><modules><add name="UrlRoutingModule" type="Manulife.Web.Mvc.Lib.Routing.UrlRoutingModule"/></modules></system.webServer>(2)新建Global全局處理配置,在Application_Start事件中為項(xiàng)目添加路由規(guī)則:
public class Global : System.Web.HttpApplication{protected void Application_Start(object sender, EventArgs e){// 注冊(cè)路由規(guī)則1 RouteTable.Routes.MapRoute(urlTemplate: "{controller}/{action}/{id}",defaults: new { controller = "Home", action = "Index" });// 注冊(cè)路由規(guī)則2 RouteTable.Routes.MapRoute(urlTemplate: "{controller}/{action}",defaults: new { controller = "Home", action = "Index" });// 注冊(cè)路由規(guī)則3 RouteTable.Routes.MapRoute(urlTemplate: "{controller}",defaults: new { controller = "Home", action = "Index" });}}(3)看看Controller是怎么寫的?是不是很熟悉?
public class HomeController : ControllerBase{public ActionResult Index(int id, string controller, string action){return new ContentResult(string.Format("<h1>Controller : {0}, Action : {1}, Id : {2}</h1>", controller, action, id), "text/html");}public ActionResult View(){return new ViewResult(new { Id = 1, Name = "Edison Chou", Age = 27, Gender = true });}}(4)看看View中的HTML呢?這里使用NVelocity模板引擎提供的語(yǔ)法,操作Model實(shí)體對(duì)象。
<!DOCTYPE html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>Index - View</title><meta charset="utf-8" /> </head> <body><h1>User Name : $model.Name</h1><h1>User Age : $model.Age</h1> </body> </html>3.2 MVC?應(yīng)用DEMO演示
(1)默認(rèn)路由 : home/index -> ContentResult
(2)請(qǐng)求JsonResult
(3)請(qǐng)求ViewResult
附件下載
Manulife.Web.Mvc?:?點(diǎn)我下載
?
作者:周旭龍
出處:http://edisonchou.cnblogs.com/
總結(jié)
以上是生活随笔為你收集整理的自己动手写一个简单的MVC框架(第二版)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己动手写一个简单的MVC框架(第一版)
- 下一篇: Windows下安装Cygwin配置Ha