MVC系列——MVC源码学习:打造本身的MVC框架(四:了解神奇的视图引擎)

前言:经过以前的三篇介绍,咱们基本上完成了从请求发出到路由匹配、再到控制器的激活,再到Action的执行这些个过程。今天仍是趁热打铁,将咱们的View也来完善下,也让整个系列相对完整,博主不但愿烂尾。对于这个系列,经过学习源码,博主也学到了不少东西,在此仍是把博主知道的先发出来,供你们参考。html

本文原创地址:http://www.cnblogs.com/landeanfen/p/6019719.htmlgit

MVC源码学习系列文章目录:github

1、自定义ActionResult

经过前三篇的介绍,咱们已经实现了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

2、ContentResult和JsonResult的实现

查看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);
            }
        }
    }
}
ContentResult.cs
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,
    }
}
JsonResult.cs

代码不难理解,就是定义了当前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的实现思路是相对比较简单的,能够说就是对当前响应流的输出作了一些封装而已。

3、解析视图引擎原理

上面实现了ContentResult和JsonResult,都是针对具体的返回值类型来定义的。除此以外,在MVC里面咱们还有一个使用最多的就是和页面html打交道的ViewResult。

一、视图引擎原理解析

1.一、若是没有视图引擎,当咱们但愿经过一个url去请求一个页面内容的时候,咱们的实现思路首先应该就是直接在后台拼Html,而后将拼好的Html交给响应流输出。上面说过,这种作法太古老,开发效率低,不易排错,而且使得先后端不能分离。这一系列的问题也反映出视图引擎的重要性。

1.二、最简单视图引擎的原理:根据博主的理解,用户经过一个Url去请求一个页面内容的时候,咱们首先定义一个静态的html页面,html里面布局和逻辑先写好,而后经过请求的url去找到这个静态的html,读取静态html里面的文本,最后将读取到的文本交由Response输出给客户端。固然,这只是一个最基础的原理,没有涉及模板以及模板语法,咱们一步一步来,先将基础原理搞懂,再说其余的。

1.三、在开始接触.net里面视图引擎以前,博主但愿根据本身的理解先自定义一个视图引擎。说作咱就作,下面就着手来试试。

二、自定义视图引擎

有了上面的原理作支撑,博主就来动手本身写一个最基础的视图引擎试试了。

2.一、首先定义一个ViewResult去实现ActionResult

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

2.二、在Controller.cs里面定义“快捷方法”

     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();
        }

2.三、添加视图ViewIndex

<!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等,下面博主依次介绍下他们各自的用法。

4、RazorEngine实现视图引擎

关于.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做为咱们框架的视图引擎就简单了。

2.一、先定义一个ActionResult的实现类。因为缓存的key必须惟一,这里使用filepath做为缓存的Key,第一次加载缓存,以后访问该页面就很快了。

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

2.二、在Controller.cs里面定义“快捷方法”

        protected virtual RazorEngineViewResult RazorEngineView()
        {
            return new RazorEngineViewResult();
        }

        protected virtual RazorEngineViewResult RazorEngineView(object data)
        {
            return new RazorEngineViewResult()
            {
                Data = data
            };
        }

2.三、在具体的控制器里面调用

    public class MyViewController:Controller
    {
       public ActionResult ViewIndex()
        {
            return RazorEngineView(new { Name = "小明", Age = 16, School = "育才高中" });
        }
    }

2.四、对应的View页面。咱们仍是用html代替,固然若是你想要用cshtml的文件,只须要改下上述文件路径便可。

<!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>

获得结果

5、NVelocity实现视图引擎

关于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();
            }

        }
    }
VelocityHelper.cs

关于Velocity模板的语法,也没啥好说的,直接在项目里面将他们搭起来试试。

一、定义ActionResult的实现类VelocityViewResult

    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()));
        }
    }

二、在Controller.cs里面添加“快捷方法”

        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代替了而已。

测试结果

6、VTemplate实现视图引擎

VTemplate模板引擎简称VT,好几年前就据说过这么一个东西,但一直没研究它的语法,感受写起来太麻烦,详情能够看看 这里。这里也不想展开说明了,由于以为原理通了,实现起来就是模板语法的不一样了,你们若是有兴趣能够本身去实现一套VT版本的视图引擎。

7、总结

总结,又到了写总结的时间了,好紧张~~这一篇主要解析了下MVC里面View的原理以及使用模板引擎的实现,至此,咱们本身的MVC基本功能都已经有了,此系列暂时告一段落吧。仍是给出源码地址:源码下载

此系列文章,花了不少时间整理,可是也收到不少园友的打赏,在此感谢你们对博主的支持和厚爱。后续博主必定继续努力,将更好的干货带给你们!

若是你以为本文可以帮助你,能够右边随意 打赏 博主,也能够 推荐 进行精神鼓励。你的支持是博主继续坚持的不懈动力。

本文原创出处:http://www.cnblogs.com/landeanfen/

欢迎各位转载,可是未经做者本人赞成,转载文章以后必须在文章页面明显位置给出做者和原文链接,不然保留追究法律责任的权利

相关文章
相关标签/搜索