前言:經過以前的三篇介紹,咱們基本上完成了從請求發出到路由匹配、再到控制器的激活,再到Action的執行這些個過程。今天仍是趁熱打鐵,將咱們的View也來完善下,也讓整個系列相對完整,博主不但願爛尾。對於這個系列,經過學習源碼,博主也學到了不少東西,在此仍是把博主知道的先發出來,供你們參考。html
本文原創地址:http://www.cnblogs.com/landeanfen/p/6019719.htmlgit
MVC源碼學習系列文章目錄:github
經過前三篇的介紹,咱們已經實現了Action方法的執行,可是咱們執行Action方法的時候都是在方法裏面直接使用Response輸出結果,這樣寫至關不爽,而且html等文本編輯實在太不方便,因而萌生了本身去實現View的想法,這個過程並不容易,但沒辦法,凡事都要勇於邁出第一步。json
在MVC裏面,咱們最多見的寫法多是這樣:後端
//返回視圖頁面 public ActionResult Index() { return View(); } //返回請求的數據 public JsonResult Index() { return Json(new {}, JsonRequestBehavior.AllowGet); }
將JsonResult轉到定義能夠看到,其實JsonResult也是繼承自ActionResult這個抽象類的,這麼神奇的ActioResult,到底是個什麼東西呢。咱們先仿照MVC裏面的也定義一個本身的ActionResult抽象類。緩存
namespace Swift.MVC.MyRazor { public abstract class ActionResult { public abstract void ExecuteResult(SwiftRouteData routeData); } }
這個類很簡單,就是一個抽象類,下面一個抽象方法,約束實現類必需要實現這個方法,究竟這個類有什麼用?且看博主怎麼一步一步去實現它。app
查看MVC源碼可知,ActionResult的實現類有不少個,博主這裏就挑幾個最經常使用的來講說。框架
在MVC裏面,ContentResult經常使用來向當前請求輸出文本內容信息,JsonResult經常使用來返回序列化過的json對象。咱們分別來實現它們:ide
namespace Swift.MVC.MyRazor { public class ContentResult:ActionResult { //頁面內容 public string Content { get; set; } //編碼方式 public Encoding ContentEncoding { get; set; } //response返回內容的格式 public string ContentType { get; set; } public override void ExecuteResult(Routing.SwiftRouteData routeData) { HttpResponse response = HttpContext.Current.Response; if (!string.IsNullOrEmpty(ContentType)) { response.ContentType = ContentType; } else { response.ContentType = "text/html"; } if (ContentEncoding != null) { response.ContentEncoding = ContentEncoding; } if (Content != null) { response.Write(Content); } } } }
namespace Swift.MVC.MyRazor { public class JsonResult:ActionResult { public JsonResult() { JsonRequestBehavior = JsonRequestBehavior.DenyGet; } public JsonRequestBehavior JsonRequestBehavior { get; set; } public Encoding ContentEncoding { get; set; } public string ContentType { get; set; } public object Data { get; set; } public override void ExecuteResult(Routing.SwiftRouteData routeData) { HttpResponse response = HttpContext.Current.Response; if (!String.IsNullOrEmpty(ContentType)) { response.ContentType = ContentType; } else { response.ContentType = "application/json"; } if (ContentEncoding != null) { response.ContentEncoding = ContentEncoding; } JavaScriptSerializer jss = new JavaScriptSerializer(); var json = jss.Serialize(Data); response.Write(json); } } public enum JsonRequestBehavior { AllowGet, DenyGet, } }
代碼不難理解,就是定義了當前Response的返回類型和編碼方式等等。接下來看看如何使用他們,爲了更加接近MVC的寫法,咱們在Controller基類裏面也定義一系列的「快捷方法」,何爲「快捷方法」,就是可以快速返回某個對象的方法,好比咱們在Controller.cs裏面增長以下幾個方法:函數
protected virtual ContentResult Content(string content) { return Content(content, null); } protected virtual ContentResult Content(string content, string contentType) { return Content(content, contentType, null); } protected virtual ContentResult Content(string content, string contentType, Encoding contentEncoding) { return new ContentResult() { Content = content, ContentType = contentType, ContentEncoding = contentEncoding }; } protected virtual JsonResult Json(object data, JsonRequestBehavior jsonBehavior) { return new JsonResult() { Data = data, JsonRequestBehavior = jsonBehavior }; }
咱們也按照MVC裏面的寫法,首先咱們新建一個控制器MyViewController.cs,裏面新增兩個方法:
namespace MyTestMVC.Controllers { public class MyViewController:Controller { public ActionResult ContentIndex() { return Content("Hello", "text/html", System.Text.Encoding.Default); } public ActionResult JsonIndex() { var lstUser = new List<User>(); lstUser.Add(new User() { Id = 1, UserName = "Admin", Age = 20, Address = "北京", Remark = "超級管理員" }); lstUser.Add(new User() { Id = 2, UserName = "張三", Age = 37, Address = "湖南", Remark = "呵呵" }); lstUser.Add(new User() { Id = 3, UserName = "王五", Age = 32, Address = "廣西", Remark = "呵呵" }); lstUser.Add(new User() { Id = 4, UserName = "韓梅梅", Age = 26, Address = "上海", Remark = "呵呵" }); lstUser.Add(new User() { Id = 5, UserName = "呵呵", Age = 18, Address = "廣東", Remark = "呵呵" }); return Json(lstUser, JsonRequestBehavior.AllowGet); } } }
看到這種用法,是否是似曾相識?注意,這裏的ActionResult並非MVC裏面的,而是上文咱們自定義的!沒錯,在原生的MVC裏面,這些方法也是這般定義的,由於以上封裝方法自己就是參考MVC的原理來實現的。
看着以上代碼,貌似大功告成,能夠直接測試運行了。是否是這樣呢?總感受少點東西呢。。。調試發現,咱們的Content()方法僅僅是返回了一個ContentResult對象,並無作其餘操做啊!按照上述定義思路,貌似應該調用ContentResult對象的ExecuteResult()方法纔對,由於這個方法裏面纔是真正的向當前的響應流裏面寫入返回信息。那麼這個ExecuteResult()方法究竟在哪裏調用呢?這裏,博主這樣調用了一下!在Controller.cs這個控制器的基類裏面,咱們修改了Execute()方法的邏輯:
public abstract class Controller:ControllerBase,IDisposable { public override void Execute(SwiftRouteData routeData) { //1.獲得當前控制器的類型 Type type = this.GetType(); //2.從路由表中取到當前請求的action名稱 string actionName = routeData.RouteValue["action"].ToString(); //3.從路由表中取到當前請求的Url參數 object parameter = null; if (routeData.RouteValue.ContainsKey("parameters")) { parameter = routeData.RouteValue["parameters"]; } var paramTypes = new List<Type>(); List<object> parameters = new List<object>(); if (parameter != null) { var dicParam = (Dictionary<string, string>)parameter; foreach (var pair in dicParam) { parameters.Add(pair.Value); paramTypes.Add(pair.Value.GetType()); } } //4.經過action名稱和對應的參數反射對應方法。 //這裏第二個參數能夠不理會action字符串的大小寫,第四個參數決定了當前請求的action的重載參數類型 System.Reflection.MethodInfo mi = type.GetMethod(actionName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase, null, paramTypes.ToArray(), null); //5.執行該Action方法 var actionResult = mi.Invoke(this, parameters.ToArray()) as ActionResult; //6.獲得action方法的返回值,並執行具體ActionResult的ExecuteResult()方法。 actionResult.ExecuteResult(routeData); } }
在以上步驟5裏面,執行對應的Action方法以後,取到返回值,這個返回值就是一個ActionResult實現類的實例,好比上文的ContentResult和JsonResult等,而後在步驟6裏面調用具體的ExecuteResult()方法。這樣應該就能解決咱們上述問題!咱們來看測試結果:
這樣,咱們這個簡單的ContentResult和JsonResult就大功告成了,縱觀整個過程,ContentResult和JsonResult的實現思路是相對比較簡單的,能夠說就是對當前響應流的輸出作了一些封裝而已。
上面實現了ContentResult和JsonResult,都是針對具體的返回值類型來定義的。除此以外,在MVC裏面咱們還有一個使用最多的就是和頁面html打交道的ViewResult。
1.一、若是沒有視圖引擎,當咱們但願經過一個url去請求一個頁面內容的時候,咱們的實現思路首先應該就是直接在後臺拼Html,而後將拼好的Html交給響應流輸出。上面說過,這種作法太古老,開發效率低,不易排錯,而且使得先後端不能分離。這一系列的問題也反映出視圖引擎的重要性。
1.二、最簡單視圖引擎的原理:根據博主的理解,用戶經過一個Url去請求一個頁面內容的時候,咱們首先定義一個靜態的html頁面,html裏面佈局和邏輯先寫好,而後經過請求的url去找到這個靜態的html,讀取靜態html裏面的文本,最後將讀取到的文本交由Response輸出給客戶端。固然,這只是一個最基礎的原理,沒有涉及模板以及模板語法,咱們一步一步來,先將基礎原理搞懂,再說其餘的。
1.三、在開始接觸.net裏面視圖引擎以前,博主但願根據本身的理解先自定義一個視圖引擎。說作咱就作,下面就着手來試試。
有了上面的原理作支撐,博主就來動手本身寫一個最基礎的視圖引擎試試了。
namespace Swift.MVC.MyRazor { public class MyViewResult : ActionResult { public object Data { get; set; } public override void ExecuteResult(Routing.SwiftRouteData routeData) { HttpResponse response = HttpContext.Current.Response; response.ContentType = "text/html"; //取當前view頁面的物理路徑 var path = AppDomain.CurrentDomain.BaseDirectory + "Views/" + routeData.RouteValue["controller"] + "/" + routeData.RouteValue["action"] + ".html"; var templateData = string.Empty; using (var fsRead = new FileStream(path, FileMode.Open)) { int fsLen = (int)fsRead.Length; byte[] heByte = new byte[fsLen]; int r = fsRead.Read(heByte, 0, heByte.Length); templateData = System.Text.Encoding.UTF8.GetString(heByte); } response.Write(templateData); } } }
protected virtual MyViewResult View() { return new MyViewResult(); } protected virtual MyViewResult View(object data) { return new MyViewResult() { Data = data }; }
而後在MyViewController.cs裏面添加Action
public ActionResult ViewIndex() { return View(); }
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> My First View </body> </html>
測試結果:
固然,這只是爲了理解視圖引擎的工做原理而寫的一個例子,實際中的視圖引擎確定要比這個複雜得多,至少得有模板吧,而後模板語法裏面還得定義後端數據映射到html裏面的規則,又得定義一套模板語法規則,其工做量不亞於一個開源項目,博主以爲就沒有必要再深刻研究這個自定義模板了,畢竟.net裏面有許多現成而且還比較好用的模板。
關於.net下面的視圖模板引擎,博主接觸過的主要有RazorEngine、NVelocity、VTemplate等,下面博主依次介紹下他們各自的用法。
關於.net裏面的模板引擎,RazorEngine算是相對好用的,它是基於微軟的Razor之上包裝而成的一個能夠獨立使用的模板引擎。也就是說,保留了Razor的模板功能,可是使得Razor脫離於Asp.net MVC,可以在其它應用環境下使用,換句話說,你徹底能夠在你的控制檯程序上面使用模板語法。
RazorEngine是一個獨立的開源項目,項目的地址是https://github.com/Antaris/RazorEngine
關於RazorEngine的使用以及具體的語法,園子裏面也是一搜一大把,這裏博主就不展開細說,只是將一些用到的方法介紹下。
要使用RazorEngine,首先必需要安裝組件,咱們使用Nuget。
安裝完成以後就能夠在咱們的.net程序裏面調用了。
先來看一個最簡單的。
string template = "姓名: @Model.Name, 年齡:@Model.Age, 學校:@Model.School"; var result = Razor.Parse(template, new { Name = "小明", Age = 16, School = "育才高中" });
我猜你已經知道結果了吧,你猜的沒錯。
是否是和MVC裏面的cshtml頁面的使用方式很是像~~它使用@Model這種做爲佔位符,動態去匹配字符串裏面的位置,從而獲得以上結果。
除此以外,RazorEngine還提供了引擎的方式,以下。
string template = "姓名: @Model.Name, 年齡:@Model.Age, 學校:@Model.School"; var result = Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "小明", Age = 16, School = "育才高中" });
結果相似:
博主調試發現,第一次獲得result的時候有點慢,查詢官方文檔才知道,第一次須要記錄緩存,緩存的key是該方法的第二個參數「templateKey」,當第二次加載的時候基本上就飛快了。
博主詳細瞭解了下RunCompile()這個方法,它是IRazorEngineService類型的一個擴展方法,這裏的四個參數都有它本身的做用:第一個是匹配的字符串;第二個是緩存的Key,上文已經說過;第三個是一個Type類型,表示第四個參數的類型,若是爲null,那麼第四個參數就爲dynamic類型;第四個參數固然就是具體的實體了。
綜合上述Razor.Parse()和Engine.Razor.RunCompile()兩種方式,按照官方的解釋,第一種是原來的用法,當你使用它的時候會發現它會提示方法已通過時,官方主推的是第二種方式。博主好奇心重試了下兩種方式的區別,發現第一次調用的時候二者耗時基本類似,刷新頁面發現,Razor.Parse()每次調用都會耗時那麼久,而RunCompile()方式進行了緩存,第一次耗時稍微多點,以後每次調用時間基本能夠忽略不計。或許這就是官方主推第二種方式的緣由吧。
看到這裏,有的小夥伴們就開始想了,既然這個模板這麼方便,那麼咱們定義一個html,裏面就按照上述template變量那麼寫,而後讀取html內容,再用模板匹配html內容是否是就能夠實現咱們的模板要求了呢?沒錯,思路確實是這樣,咱們來試一把。
var filepath = AppDomain.CurrentDomain.BaseDirectory + @"Views\" + routeData.RouteValue["controller"] + "\\" + routeData.RouteValue["action"] + ".html"; var fileContent = Engine.Razor.RunCompile(File.ReadAllText(filepath), "templateKey2", null, new { Name = "小明", Age = 16, School = "育才高中" });
而後對應的html內容以下:
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> 姓名: @Model.Name, 年齡:@Model.Age, 學校:@Model.School </body> </html>
獲得結果
就是這麼簡單!
有了上面的一些嘗試做爲基礎,將RazorEngine做爲咱們框架的視圖引擎就簡單了。
namespace Swift.MVC.MyRazor { public class RazorEngineViewResult:ActionResult { public object Data { get; set; } public override void ExecuteResult(Routing.SwiftRouteData routeData) { var filepath = AppDomain.CurrentDomain.BaseDirectory + @"Views\" + routeData.RouteValue["controller"] + "\\" + routeData.RouteValue["action"] + ".html"; var fileContent = Engine.Razor.RunCompile(File.ReadAllText(filepath), filepath, null, Data); HttpResponse response = HttpContext.Current.Response; response.ContentType = "text/html"; response.Write(fileContent); } } }
protected virtual RazorEngineViewResult RazorEngineView() { return new RazorEngineViewResult(); } protected virtual RazorEngineViewResult RazorEngineView(object data) { return new RazorEngineViewResult() { Data = data }; }
public class MyViewController:Controller { public ActionResult ViewIndex() { return RazorEngineView(new { Name = "小明", Age = 16, School = "育才高中" }); } }
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> 姓名: @Model.Name, 年齡:@Model.Age, 學校:@Model.School </body> </html>
獲得結果
關於NVelocity模板引擎,博主簡單從網上down了一個Helper文件。要使用它,首先仍是得安裝組件
首先給出VelocityHelper
/// <summary> /// NVelocity模板工具類 VelocityHelper /// </summary> public class VelocityHelper { private VelocityEngine velocity = null; private IContext context = null; /// <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(); } } }
關於Velocity模板的語法,也沒啥好說的,直接在項目裏面將他們搭起來試試。
public class VelocityViewResult:ActionResult { public object Data { get; set; } public override void ExecuteResult(Routing.SwiftRouteData routeData) { //這裏必須是虛擬路徑 var velocity = new VelocityHelper(string.Format("~/Views/{0}/", routeData.RouteValue["controller"])); // 綁定實體model velocity.Put("model", Data); // 顯示具體html HttpResponse response = HttpContext.Current.Response; response.ContentType = "text/html"; velocity.Display(string.Format("{0}.cshtml", routeData.RouteValue["action"].ToString())); } }
protected virtual VelocityViewResult VelocityView() { return new VelocityViewResult(); } protected virtual VelocityViewResult VelocityView(object data) { return new VelocityViewResult() { Data = data }; }
public class MyViewController:Controller { public ActionResult ViewIndex() { return VelocityView(new { Name = "小明", Age = 16, School = "育才高中" }); } }
上面咱們在測試RazorEngine引擎的時候,使用的是html代替,這裏咱們改用cshtml後綴的模板文件ViewIndex.cshtml
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title></title> </head> <body> <div> <h1>姓名: $model.Name</h1> <h1>年齡:$model.Age</h1> <h1>學校:$model.School</h1> </div> </body> </html>
這裏就是和RazorEngine不同的地方,不過很好理解,原來的@Model這裏用$model代替了而已。
測試結果
VTemplate模板引擎簡稱VT,好幾年前就據說過這麼一個東西,但一直沒研究它的語法,感受寫起來太麻煩,詳情能夠看看 這裏。這裏也不想展開說明了,由於以爲原理通了,實現起來就是模板語法的不一樣了,你們若是有興趣能夠本身去實現一套VT版本的視圖引擎。
總結,又到了寫總結的時間了,好緊張~~這一篇主要解析了下MVC裏面View的原理以及使用模板引擎的實現,至此,咱們本身的MVC基本功能都已經有了,此係列暫時告一段落吧。仍是給出源碼地址:源碼下載。
此係列文章,花了不少時間整理,可是也收到不少園友的打賞,在此感謝你們對博主的支持和厚愛。後續博主必定繼續努力,將更好的乾貨帶給你們!
若是你以爲本文可以幫助你,能夠右邊隨意 打賞 博主,也能夠 推薦 進行精神鼓勵。你的支持是博主繼續堅持的不懈動力。
本文原創出處:http://www.cnblogs.com/landeanfen/
歡迎各位轉載,可是未經做者本人贊成,轉載文章以後必須在文章頁面明顯位置給出做者和原文鏈接,不然保留追究法律責任的權利