自己動手寫一個簡單的MVC框架(第二版)
一、ASP.NET MVC核心機制回顧
在ASP.NET MVC中,最核心的當屬“路由系統”,而路由系統的核心則源於一個強大的System.Web.Routing.dll元件。
在這個System.Web.Routing.dll中,有一個最重要的類叫做UrlRoutingModule,它是一個實現了IHttpModule介面的類,在請求處理管道中專門針對ASP.NET MVC請求進行處理。首先,我們要了解一下UrlRoutingModule是如何起作用的。
(1)IIS網站的配置可以分為兩個塊:全域性 Web.config 和本站 Web.config。Asp.Net Routing屬於全域性性的,所以它配置在全域性Web.Config 中,我們可以在如下路徑中找到:“$\Windows\Microsoft.NET\Framework\版本號\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中註冊 System.Web.Routing.UrlRoutingModule,IIS請求處理管道接到請求後,就會載入 UrlRoutingModule型別的Init()方法。
PS : 在UrlRoutingModule中為請求處理管道中的第七個事件PostResolveRequestCache註冊了一個事件處理方法:OnApplicationPostResolveRequestCache。從這裡可以看出:ASP.NET MVC的入口在UrlRoutingModule,即訂閱了HttpApplication的第7個管道事件PostResolveRequestCahce。換句話說,是在HtttpApplication的第7個管道事件處對請求進行了攔截
。
現在我們將ASP.NET MVC的請求處理分為兩個重要階段來看看:
①在第七個事件中建立實現了IHttpHandler介面的MvcHandler
當請求到達UrlRoutingModule的時候,UrlRoutingModule取出請求中的Controller、Action等RouteData資訊,與路由表中的所有規則進行匹配,若匹配,把請求交給IRouteHandler,即MVCRouteHandler。我們可以看下UrlRoutingModule的原始碼來看看,以下是幾句核心的程式碼:
public virtual void PostResolveRequestCache(HttpContextBase context) { // 通過RouteCollection的靜態方法GetRouteData獲取到封裝路由資訊的RouteData例項 RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { // 再從RouteData中獲取MVCRouteHandler IRouteHandler routeHandler = routeData.RouteHandler; ...... if (!(routeHandler is StopRoutingHandler)) { ...... // 呼叫 IRouteHandler.GetHttpHandler(),獲取的IHttpHandler 型別例項,它是由 IRouteHandler.GetHttpHandler獲取的,這個得去MVC的原始碼裡看 IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); ...... // 合適條件下,把之前將獲取的IHttpHandler 型別例項 對映到IIS HTTP處理管道中 context.RemapHandler(httpHandler); } } }View Code
從原始碼片段中可以看出,最後將請求轉移給了實現了IHttpHandler介面的處理程式進行後續的處理。在ASP.NET MVC的實現中,是將請求交給了MvcHandler這個類,通過執行其ProcessRequest方法來進行後續的處理。
②在第十一個事件與第十二個事件之間呼叫MvcHandler的ProcessRequest()方法
(1)在WebForm中,此階段會呼叫Page類物件的ProcessRequest()方法。在ASP.NET MVC中,會呼叫MvcHandler的ProcessRequest()方法,此方法會啟用具體請求的Controller類物件,觸發Action方法,返回ActionResult例項。
(2)如果ActionResult是非ViewResult,比如JsonResult, ContentResult,這些內容將直接被輸送到Response響應流中,顯示給客戶端;如果是ViewResult,就會進入下一個渲染檢視環節。
(3)在渲染檢視環節,ViewEngine找到需要被渲染的檢視,View被載入成WebViewPage<TModel>型別,並渲染生成Html,最終返回Html。
二、我的MVC框架核心部分介紹
2.1 解決方案概覽
在該解決方案中,一共有兩個專案:
一個是App,它是一個由最小化的引用環境(只引用了System和System.Web,以及Mvc.Lib)搭建起來的一個Web應用專案,藉助MVC核心類庫(Mvc.Lib)實現了MVC模式。
一個是Lib,它是一個模擬ASP.NET MVC框架的最小化、輕量級的迷你MVC框架,其中Mvc資料夾模擬System.Web.Mvc,Routing資料夾模擬System.Web.Routing,而View則簡單地藉助NVelocity模板引擎提供View檢視服務。
2.2 MVC核心類庫
(1)Routing
從第一部分我們可以知道,ASP.NET MVC的入口在於UrlRoutingModule,因此這裡我們便模擬實現了一個UrlRoutingModule.
/// <summary> /// 解析請求中的路由資料,並分發請求到Handler /// </summary> public class UrlRoutingModule : IHttpModule { public void Init(HttpApplication application) { // 註冊ASP.NET請求處理管道的第七個事件 application.PostResolveRequestCache += Application_PostResolveRequestCache; } // 假設請求 http://www.edisonchou.cn/home/index private void Application_PostResolveRequestCache(object sender, EventArgs e) { var application = sender as HttpApplication; var context = application.Context; // 根據全域性路由表解析當前請求的路徑 var requestUrl = context.Request.AppRelativeCurrentExecutionFilePath.Substring(2); // 遍歷全域性路由表中的路由規則解析資料 IDictionary<string, object> routeData; var route = RouteTable.MatchRoutes(requestUrl, out routeData); if (route == null) { // 404 Not Found throw new HttpException(404, "Not Found!"); } // 獲取處理請求的Handler處理程式 if (!routeData.ContainsKey("controller")) { // 404 Not Found throw new HttpException(404, "Not Found!"); } var handler = route.GetRouteHandler(routeData); // 為當前請求指定Handler處理程式 context.RemapHandler(handler); } public void Dispose() { this.Dispose(); } }
該UrlRoutingModule通過註冊ASP.NET請求處理管道的第七個事件,來實現對URL地址進行路由規則的處理,並將最後生成的路由資料交給MvcHandler進行後續處理。這裡我省略了ASP.NET MVC原始碼中MvcRouteHandler生成MvcHandler的步驟,直接丟給MvcHandler處理。
核心部分有兩點,一是路由規則的匹配,二是為請求指定handler。
在路由規則的匹配中,通過設定路由資料鍵值對(Dictionary),並將設定好的路有資料傳遞給MvcHandler。具體的流程如下圖所示,這裡就不再展示原始碼,請自行下載DEMO檢視:
(2)Mvc
在此資料夾中,實現了三個核心的部分:
① 最核心的處理者 : 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(); // 藉助控制器工廠建立具體控制器例項 IController controller = DefaultControllerFactory.CreateController(controllerName); // 確保有找到一個Controller處理請求 if (controller == null) { // 404 Not Found! throw new HttpException(404, "Not Found"); } // 封裝請求 var requestContext = new RequestContext { HttpContext = context, RouteData = routeData }; // 開始執行 var result = controller.Execute(requestContext); result.Execute(requestContext); } public bool IsReusable { get { return false; } } }
在MvcHandler類中,主要經歷了以下事件:
② 花樣的返回型別 : ActionResult 以及它的子類們
在以往的ASP.NET MVC開發中,我們在Action方法的編寫中,總會看到它們的返回型別都是以ActionResult為基類的各種Result型別。
/// <summary> /// Action統一的返回型別 /// </summary> public abstract class ActionResult { public abstract void Execute(RequestContext context); }
因此,這裡也實現了ActionResult這個抽象類,並以此為基礎實現了ContentResult、JsonResult以及ViewResult。它們的區別就在於是不同的返回型別,因此有不同的處理。
這裡以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"; } }
相信有經驗的讀者一眼就看穿了,因此這裡也就不再多說了。
③ 路由的擴充套件者 : RouteExtend
在以往的ASP.NET MVC開發中,我們會在Global全域性應用處理檔案中為專案註冊路由規則,但卻不知道其實我們常用的MapRoute方法其實是一個擴充套件方法,它並不位於System.Web.Routing這個類庫之中,而是位於System.Web.Mvc這個類庫之中。
因此,我們也在Mvc資料夾中實現了一個RouteExtend類,它為RouteTable類的Route集合實現了一個擴充套件方法:
/// <summary> /// Route 的擴充套件方法所在類 /// </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> /// 通過指定實現了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是單向依賴)。那麼,為什麼還要提供一個可傳入自定義Handler的介面呢?因為,不同的路由規則有可能需要不同的實現IHttpHandler的處理程式來處理,也不一定就非得是MvcHandler。
(3)View
在ASP.NET MVC中提供了aspx與Razor等模板引擎,這裡我偷了懶,直接藉助了NVelocity模板引擎來實現。因此,這個資料夾中只有一個VelocityHelper類(我直接從網上搜索的),該類可以幫助我們找到指定的HTML並繫結Model實體。
/// <summary> /// NVelocity模板工具類 VelocityHelper /// </summary> public class VelocityHelper { private VelocityEngine velocity = null; private IContext context = null; public object YZControl { get; private set; } /// <summary> /// 建構函式 /// </summary> /// <param name="templatDir">模板資料夾路徑</param> public VelocityHelper(string templatDir) { Init(templatDir); } /// <summary> /// 無引數建構函式 /// </summary> public VelocityHelper() { } /// <summary> /// 初始話NVelocity模組 /// </summary> public void Init(string templatDir) { // 建立VelocityEngine例項物件 velocity = new VelocityEngine(); // 使用設定初始化VelocityEngine ExtendedProperties props = new ExtendedProperties(); props.AddProperty(RuntimeConstants.RESOURCE_LOADER, "file"); props.AddProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, HttpContext.Current.Server.MapPath(templatDir)); //props.AddProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, Path.GetDirectoryName(HttpContext.Current.Request.PhysicalPath)); props.AddProperty(RuntimeConstants.INPUT_ENCODING, "utf-8"); props.AddProperty(RuntimeConstants.OUTPUT_ENCODING, "utf-8"); // 模板的快取設定 props.AddProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, true); //是否快取 props.AddProperty("file.resource.loader.modificationCheckInterval", (Int64)30); //快取時間(秒) velocity.Init(props); // 為模板變數賦值 context = new VelocityContext(); } /// <summary> /// 給模板變數賦值 /// </summary> /// <param name="key">模板變數</param> /// <param name="value">模板變數值</param> public void Put(string key, object value) { if (context == null) { context = new VelocityContext(); } context.Put(key, value); } /// <summary> /// 顯示模板 /// </summary> /// <param name="templatFileName">模板檔名</param> public void Display(string templatFileName) { // 從檔案中讀取模板 Template template = velocity.GetTemplate(templatFileName); // 合併模板 StringWriter writer = new StringWriter(); template.Merge(context, writer); // 輸出 HttpContext.Current.Response.Clear(); HttpContext.Current.Response.Write(writer.ToString()); HttpContext.Current.Response.Flush(); HttpContext.Current.Response.End(); } /// <summary> /// 根據模板生成靜態頁面 /// </summary> /// <param name="templatFileName"></param> /// <param name="htmlpath"></param> public void CreateHtml(string templatFileName, string htmlpath) { // 從檔案中讀取模板 Template template = velocity.GetTemplate(templatFileName); // 合併模板 StringWriter writer = new StringWriter(); template.Merge(context, writer); using (StreamWriter write2 = new StreamWriter(HttpContext.Current.Server.MapPath(htmlpath), false, Encoding.UTF8, 200)) { write2.Write(writer); write2.Flush(); write2.Close(); } } /// <summary> /// 根據模板生成靜態頁面 /// </summary> /// <param name="templatFileName"></param> /// <param name="htmlpath"></param> //public void CreateJS(string templatFileName, string htmlpath) //{ // //從檔案中讀取模板 // Template template = velocity.GetTemplate(templatFileName); // //合併模板 // StringWriter writer = new StringWriter(); // template.Merge(context, writer); // using (StreamWriter write2 = new StreamWriter(HttpContext.Current.Server.MapPath(htmlpath), false, Encoding.UTF8, 200)) // { // write2.Write(YZControl.Strings.Html2Js(YZControl.Strings.ZipHtml(writer.ToString()))); // write2.Flush(); // write2.Close(); // } //} }View Code
三、我的MVC框架應用例項
3.1 MVC 應用DEMO介紹
這是一個ASP.NET 空Web應用專案搭建起來的MVC Web應用專案,它移除了自帶的所有引用專案,僅僅保留了System和System.Web,做到了儘可能地“純淨”。通過引入Mvc.Lib核心類庫,建立Controller、Model和View資料夾以及對應的類和HTML來實現MVC模式。
(1)引入Mvc.Lib核心類庫之後,需要配置一下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> <!-- 配置不去校驗是否是整合模式 --> <validation validateIntegratedModeConfiguration="false"/> <!-- HttpModule配置(IIS7及以上版本) --> <modules> <add name="UrlRoutingModule" type="Manulife.Web.Mvc.Lib.Routing.UrlRoutingModule"/> </modules> </system.webServer>
(2)新建Global全域性處理配置,在Application_Start事件中為專案新增路由規則:
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { // 註冊路由規則1 RouteTable.Routes.MapRoute( urlTemplate: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index" } ); // 註冊路由規則2 RouteTable.Routes.MapRoute( urlTemplate: "{controller}/{action}", defaults: new { controller = "Home", action = "Index" } ); // 註冊路由規則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模板引擎提供的語法,操作Model實體物件。
<!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 應用DEMO演示
(1)預設路由 : home/index -> ContentResult
(2)請求JsonResult
(3)請求ViewResult
附件下載
Manulife.Web.Mvc : 點我下載
作者:周旭龍
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。
相關推薦
自己動手寫一個簡單的MVC框架(第二版)
一、ASP.NET MVC核心機制回顧 在ASP.NET MVC中,最核心的當屬“路由系統”,而路由系統的核心則源於一個強大的System.Web.Routing.dll元件。 在這個System.Web.Routing.dll中,有一個最重要的類叫做UrlRoutingModule,它是一個
自己動手寫一個簡單的MVC框架(第一版)
一、MVC概念回顧 路由(Route)、控制器(Controller)、行為(Action)、模型(Model)、檢視(View) 用一句簡單地話來描述以上關鍵點: 路由(Route)就相當於一個公司的前臺小姐,她負責帶你(請求)找到跟你面試的面試官(控制器Controller),面試官
自己動手實現一個簡單的Mybatis(初級版本1.0)
手寫Mybatis-v1.0 原始碼連結(包括v1.0與v2.0): https://github.com/staticLin/customMyBatis.git 從上一個文章 ---Mybatis概述中瞭解到了Mybatis的主要架構與底層原理流程,結尾給出了一個巨集觀流程圖,可
死磕 java執行緒系列之自己動手寫一個執行緒池(續)
(手機橫屏看原始碼更方便) 問題 (1)自己動手寫的執行緒池如何支援帶返回值的任務呢? (2)如果任務執行的過程中丟擲異常了該
自己動手寫一個持久層框架
[TOC] ## 0. 前言 and Flag 不得不說我又買課了,之前買課都花了大幾百了,但是這次又沒躲過去。買了拉鉤教育的【**java高薪訓練營**】。主要看到那個課程目錄有點牛逼,基本上把我聽說過的技術都包括了,不過真假不太確定,之後就是知乎百度谷歌一頓搜尋,沒查到什麼負面資訊,B站也有一部分視訊,我
《TensorFlow:實戰Google深度學習框架(第二版)》筆記【1-6章】
第一章:深度學習簡介 在大部分情況下,在訓練資料達到一定數量之前,越多的訓練資料可以使邏輯迴歸演算法對未知郵件做出的判斷越精準。之所以說在大部分情況下,是因為邏輯迴歸演算法的效果除了依賴於訓練資料,也依賴於從資料中提取的特徵。假設從郵件中抽取的特徵只有郵件傳送
PHP MVC框架基礎小白(自己動手寫一個PHP框架示例)
一個示例專案,具體展示了PHP,MVC模式框架開發的全過程本人小白一個,希望各位大神多多指教,由於教程文字過多而且不易解釋我直接將示例專案打包,下面附上鍊接各位對PHP框架學習可以借鑑專案內帶有其他方法和註釋,希望能對大家有所幫助
自己動手寫一個Vue外掛(MD.7)
造不完的輪子,封不完的外掛。網上什麼都有,但是有那找的功夫,自己都寫完了。漫島仍然在向前推進,只是你們看不到最新的更新內容了,剩餘的不會展示,等以後上線了再去看把。 講一下如何寫一個的Vue外掛,(以一個極其簡單的loading效果為例),會了這個其他不愁。 第一步,在compon
自己動手寫一個輕量級的Android網路請求框架
最近有空在看《App研發錄》一書,良心之作。書中第一部分第二章節講了不少關於網路底層封裝的知識,看後覺得學到了不少乾貨。 索性自己也動手完成了一個非常輕量級的網路請求框架,從該書中獲得了不少幫助。特此記錄,回顧一下思路,整理收穫。OK,一起來看。 就如書中所
ROS的初步學習(五)--自己寫一個簡單的釋出(Publisher)、訂閱(Subscriber)程式
1 寫一個釋出(Publisher)節點 節點(node)是連線到ROS網路中可執行的基本單元。我們在這建立一個釋出者—“talker”節點,這個節點持續對外發布訊息。 首先我們要把目錄切換到我們的beginner_tutorials工程包中 $ cd ~
自己動手寫一個自動登錄腳本gg
簡單 只需要 自己 不同 enum -s class rep 使用 1.下載一個sshpass工具 2.安裝sshpass,安裝到tools文件夾 3.把tools文件夾的路徑加入到/etc/bashrc vim /etc/bashrc
自己動手實現一個簡單的JSON解析器
pair bool 優點 輕量 結束 pan isdigit 復雜 false 1. 背景 JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。相對於另一種數據交換格式 XML,JSON 有著諸多優點。比如易讀性更好,占用空間更少等
自己動手寫一個單鏈表
兩個指針 isl linklist nextn mob 內部 數組 nds pty 文章有不當之處,歡迎指正,如果喜歡微信閱讀,你也可以關註我的微信公眾號:好好學java,獲取優質學習資源。 一、概述 單向鏈表(單鏈表)是鏈表的一種,其特點是鏈表的鏈接方向是單向的,對鏈表
【原創】自己動手寫一個服務網關
exception 負責 lis world 前置 create ble ddr load 引言 什麽是網關?為什麽需要使用網關? 如圖所示,在不使用網關的情況下,我們的服務是直接暴露給服務調用方。當調用方增多,勢必需要添加定制化訪問權限、校驗等邏輯。當添加API網關後,
UGUI揹包,使用MVC框架(c#語言),模擬揹包物品載入到自己的揹包
最近也就是了解了下MVC揹包模式,然後試著做了一下,先展示下效果圖吧: 左邊是我虛擬的商店物品,當然你也可以當成另一個倉庫。右邊是另一個倉庫。 左邊的揹包可以看出,是可以隨意換位置的,而且左邊的揹包換位置後放進自己的揹包是不受影響的。 ChessInto程式碼(這段程式碼
自己動手寫一個小型的TCP/IP協議
TCP/IP協議大家都知道,但真正理解的人不多,不如動手寫一個小型的看看。 我知道看書很枯燥,看不懂,還打擊大家的信心,不是我們的腦袋不如人,是我們的方法錯了。 一切的技術都從應用中發展而來,所以要從下往上走,先動手完成一個任務吧。 需要準備的前提知識 linux驅動程式知識
java小白自己動手開發一個網站之立項(第1回)
新手小白,大神們看到什麼問題,請多多指出 MyWeb專案立項 修訂記錄表 修訂人 修訂版本 修訂描述 修訂時間 備註
從零寫一個Java WEB框架(七)Controller層轉換器
該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github
從零寫一個Java WEB框架(六)Controller層優化
該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github
從零寫一個Java WEB框架(五)IOC建立
該系列,其實是對《架構探險》這本書的實踐。本人想記錄自己的學習心得所寫下的。 從一個簡單的Servlet專案開始起步。對每一層進行優化,然後形成一個輕量級的框架。 每一篇,都是針對專案的不足點進行優化的。 專案已放上github