自己动手写一个简单的MVC框架(第一版)
一、MVC概念回顧
路由(Route)、控制器(Controller)、行為(Action)、模型(Model)、視圖(View)
用一句簡單地話來描述以上關鍵點:
路由(Route)就相當于一個公司的前臺小姐,她負責帶你(請求)找到跟你面試的面試官(控制器Controller),面試官可能會面試不同的職位(Action),你(請求)也會拿到不同的結果(ActionResult);
二、開始DEMO:單一處理程序入口
2.1 創建一個空白Web程序,移除所有默認引用
無論是ASP.NET WebForms還是ASP.NET MVC,他們都只是一個框架,是建立在System.Web之上的框架。為了保證程序的純凈,我們可以將所有默認的引用都移除。當然,我們還是得保留幾個必要的dll引用:
注意:這里我們并沒有引入System.Web.Mvc.dll,因為我們要實現的就是一個簡單的MVC機制。
2.2 模擬ASP.NET MVC,創建幾個MVC文件夾
按照ASP.NET MVC的慣例添加Controllers、Models和Views文件夾(不是必須的):
2.3 新建一個Controller
我們首先在Controllers文件夾下新建一個接口,取名為IController,它約定了所有Controller都必須要實現的方法:Execute
public interface IController{void Execute(HttpContext context);}IController接口只定義了一個方法聲明,它接收一個HttpContext的上下文對象。
有了接口,我們就可以實現具體的Controller了,這里我們實現了兩個Controller:HomeController和ProductController。
(1)HomeController
View Code(2)ProductController
View Code2.4 新建一個ashx(一般處理程序),作為處理程序的入口
有了Controller之后,需要借助一個入口來指引請求到達指定Controller,所以這里我們實現一個最簡單的一般處理程序,它將url中的參數進行解析并實例化指定的Controller進行后續請求處理:
View Code該一般處理程序接收http請求的兩個參數controller和action,并通過controller的參數名稱生成對應的Controller實例對象,將HttpContext對象作為參數傳遞給對應的Controller對象進行后續處理。
2.5 新建一個Global(全局處理程序),作為路由映射的入口
在Global.asax中有一個Application_BeginRequest的事件,它發生在每個Request開始處理之前,因此在這里我們可以進行一些類似于URL重寫的工作。解析URL當然也在這里進行,我們要做的就是將用戶輸入的類似于MVC形式的URL:http://www.xxx.com/home/index 進行正確的解析,將該請求交由HomeController進行處理。
View Code這里我們直接在代碼中hardcode了一個默認的controller和action名稱,分別是home和index。
可以看出,最后我們實際上做的就是解析URL,并通過重定向到Index.ashx進行所謂的Route路由工作。
2.6 運行吧偽MVC
(1)默認路由
(2)/home/add
(3)/product/index
三、改造DEMO:借助反射讓多態發光
3.1 在Global文件中模擬路由規則表
想想我們在ASP.NET MVC項目中是不是首先向程序注冊一些指定的路由規則,因此這里我們也在Global.asax中模擬一個路由規則表:
(1)增加一個靜態的路由規則集合
// 定義路由規則private static IList<string> Routes;(2)在Application_Start事件中注冊路由規則
protected void Application_Start(object sender, EventArgs e){Routes = new List<string>();// http://www.edisonchou.cn/controller/actionRoutes.Add("{controller}/{action}");// http://www.edisonchou.cn/controllerRoutes.Add("{controller}");}(3)改寫Application_BeginRequest事件,使URL與路由規則進行匹配
protected void Application_BeginRequest(object sender, EventArgs e){#region 方式二:模擬路由表實現映射服務// 模擬路由字典IDictionary<string, string> routeData = new Dictionary<string, string>();// 將URL與路由表中每一條記錄進行匹配foreach (var item in Routes){var executePath = Request.AppRelativeCurrentExecutionFilePath;//// 獲得當前請求的參數數組// 如果沒有參數則執行默認配置if (string.IsNullOrEmpty(executePath) || executePath.Equals("~/")){executePath += "/home/index";}var executePathArray = executePath.Substring(2).Split(new[] { '/' },StringSplitOptions.RemoveEmptyEntries);var routeKeys = item.Split(new[] { '/' },StringSplitOptions.RemoveEmptyEntries);if (executePathArray.Length == routeKeys.Length){for (int i = 0; i < routeKeys.Length; i++){routeData.Add(routeKeys[i], executePathArray[i]);}// 入口一:單一入口 Index.ashx//Context.RewritePath(string.Format("~/Index.ashx?c={0}&a={1}", routeData["{controller}"], routeData["{action}"]));// 入口二:指定MvcHandler進行后續處理Context.RemapHandler(new MvcHandler(routeData));// 只要滿足一條規則就跳出循環匹配break;}}#endregion}3.2 模擬ASP.NET管道工作,實現MvcHandler
在ASP.NET請求處理管道中,具體的處理工作都是轉交給了實現IHttpHandler接口的Handler對象進行處理。因此,這里我們也遵照這個規則,實現一個MvcHandler來代替剛才的Index.ashx來進行路由工作:
View Code上述代碼中需要注意以下幾點:
(1)在靜態構造函數中初始化所有Controller
// 路由表private IDictionary<string, string> routeData;// 所有控制器的類型集合private static IList<Type> alloctionControllerTypes;// 當前類第一次加載時調用靜態構造函數static MvcHandler(){alloctionControllerTypes = new List<Type>();// 獲得當前所有引用的程序集var assemblies = BuildManager.GetReferencedAssemblies();// 遍歷所有的程序集foreach (Assembly assembly in assemblies){// 獲取當前程序集中所有的類型var allTypes = assembly.GetTypes();// 遍歷所有的類型foreach (Type type in allTypes){// 如果當前類型滿足條件if (type.IsClass && !type.IsAbstract && type.IsPublic&& typeof(IController).IsAssignableFrom(type)){// 將所有Controller加入集合 alloctionControllerTypes.Add(type);}}}}此段代碼利用反射加載了所有實現了IController接口的Controller類,并存入了一個靜態集合alloctionControllerTypes里面,便于后面所有請求進行匹配。
(2)在ProcessRequest方法中再次利用反射動態創建Controller實例
public void ProcessRequest(HttpContext context){var controllerName = routeData["{controller}"];if (string.IsNullOrEmpty(controllerName)){// 指定默認控制器controllerName = "home";}IController controller = null;// 通過反射的方式加載具體實例foreach (var controllerItem in alloctionControllerTypes){if (controllerItem.Name.Equals(string.Format("{0}Controller", controllerName), StringComparison.InvariantCultureIgnoreCase)){controller = Activator.CreateInstance(controllerItem) as IController;break;}} var requestContext = new HttpContextWrapper(){Context = context,RouteData = routeData};controller.Execute(requestContext);}這里由于要使用到RouteData這個路由表的Dictionary對象,所以我們需要改寫一下傳遞的對象由原來的HttpContext類型轉換為自定義的包裝類HttpContextWrapper:
public class HttpContextWrapper{public HttpContext Context { get; set; }public IDictionary<string, string> RouteData { get; set; }}可以看出,其實就是簡單地包裹了一下,添加了一個RouteData的路由表屬性。
當然,IController接口的方法定義也得隨之改一下:
public interface IController{void Execute(HttpContextWrapper context);}至此,MvcHandler的代碼就寫完,我們可以總結一下它的主要流程:
3.3 改寫Controller匹配新接口
(1)HomeController
View Code(2)ProductController
View Code3.4 運行吧偽MVC
(1)默認路由
(2)/product/add
(3)/product
四、小結
本文首先回顧了一下MVC的關鍵概念,并從一個“純凈”的ASP.NET Web空項目開始一步一步構建一個類似于MVC的應用程序,通過單一處理入口的偽靜態方式與模擬路由表的方式進行了簡單地實現,并進行了測試。此次實驗,核心就在于獲取路由數據,指定處理程序,也就是理解并模擬路由機制。路由模塊就是一個很簡單的HttpModule(如果您對HttpModule不熟悉,請瀏覽我翻譯的一篇文章:ASP.NET應用程序和頁面生命周期),而ASP.NET MVC幫我們實現了UrlRoutingModule從而使我們輕松實現了路由機制,該機制獲取了路由數據,并制定處理程序(如MvcHandler),執行MvcHandler的ProcessRequest方法找到對應的Controller類型,最后將控制權交給對應的Controller對象,就相當于前臺小妹妹幫你找到了面試官,你可以跟著面試官去進行相應的面試了(Actioin),希望你能得到好的結果(ActionResult)。
當然,這個DEMO還有很多需要改進的地方,仍然需要不斷的改進才能稱之為一個“框架”。第一個版本就到此,后續我會寫第二個版本,希望到時再寫一篇筆記來分享。
附件下載
MySimpleMvc?: 點我下載
?
作者:周旭龍
出處:http://edisonchou.cnblogs.com/
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的自己动手写一个简单的MVC框架(第一版)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大志非才不就,大才非学不成—博文资源汇总
- 下一篇: 自己动手写一个简单的MVC框架(第二版)