首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >ASP.NET MVC的View是如何被呈现出来的?[设计篇]

ASP.NET MVC的View是如何被呈现出来的?[设计篇]

作者头像
蒋金楠
发布于 2018-01-15 07:49:51
发布于 2018-01-15 07:49:51
1.1K0
举报
文章被收录于专栏:大内老A大内老A

在前面的四篇文章中,我们介绍了各种ActionResult以及相关的请求响应机制,但是与“View的呈现”相关的ActionResult是ViewResult。通过ViewResult的执行实现的对View的呈现比上面我们介绍的各种ActionResult要复杂得多,ASP.NET MVC内部设计了一个扩展的View引擎实现了最终的View呈现工作。[本文已经同步到《How ASP.NET MVC Works?》中]

目录 一、View引擎中的View 二、ViewEngine 三、ViewResult的执行

一、View引擎中的View

ASP.NET MVC为我们提供了两种View引擎,它们针对不同的动态View设计方式。一种是传统的Web Form引擎,由于该引擎下View的设计与我们定义.aspx页面一致,又称为ASPX引擎。另外一种则是本书默认采用同时也是推荐使用的Razor引擎。在两种View引擎的工作机制之前,有一个必须要知道的问题:View如何表示?提到View,很多ASP.NET MVC的开发人员可能首先想到的就是定义UI界面的.aspx文件(Web Form引擎)或者.cshtml/.vbhtml文件(Razor引擎)。其实对于View引擎来说,View是一个实现了IView接口类型的对象。如下面的代码片断所示,IView的定义非常简单,仅仅具有唯一的Render方法根据指定的View上下文和TextWriter对象实现对View的呈现。

代码语言:js
AI代码解释
复制
   1: public interface IView
   2: {    
   3:     void Render(ViewContext viewContext, TextWriter writer);
   4: }
   5:  
   6: public class ViewContext : ControllerContext
   7: {
   8:     //其他成员
   9:     public virtual bool ClientValidationEnabled { get; set; }
  10:     public virtual bool UnobtrusiveJavaScriptEnabled { get; set; }
  11:  
  12:     public virtual TempDataDictionary TempData { get; set; }    
  13:     [Dynamic]
  14:     public object                     ViewBag { [return: Dynamic] get; }
  15:     public virtual ViewDataDictionary ViewData { get; set; }
  16:     public virtual IView              View { get; set; }
  17:     public virtual TextWriter         Writer { get; set; }
  18: }
  19:  
  20: public abstract class HttpResponseBase
  21: {
  22:     //其他成员
  23:     public virtual TextWriter Output { get; set; }
  24: }

IView用于呈现View的Render方法具有两个参数,一个是表示View上下文的ViewContext对象。通过上面的代码片断可以看出ViewContext是ControllerContext的子类,用于表示状态数据的ViewData、ViewBag和TempData对应着ControllerBase的同名属性。也就是说当执行从Controller的某个Action方法返回的ViewResult的时候,通过创建的ViewContext保持的状态数据直接来源于Controller对象。

ViewContext具有两个布尔类型属性ClientValidationEnabled和UnobtrusiveJavaScriptEnabled表示是否支持客户端验证和Unobtrusive JavaScript。默认的情况下着两个属性通过同名的AppSettings配置项进行设置。如果应用不具有对应的配置,两个属性默认值为False。

代码语言:js
AI代码解释
复制
   1: <configuration>
   2:   <appSettings>
   3:     <add key="ClientValidationEnabled" value="true"/>
   4:     <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
   5:   </appSettings>
   6: </configuration>

配置的范围是针对整个Web应用而言的,这个全局属性还可以通过HtmlHelper的同名静态属性进行设置。值得一提的是,ASP.NET MVC 允许我们针对某个View开启或者关闭对客户端验证和UnobtrusiveJavaScriptEnabled的支持,而这可以通过当前View的HtmlHelper的实例方法EnableClientValidation/EnableUnobtrusiveJavaScript来实现。

代码语言:js
AI代码解释
复制
   1: public class HtmlHelper
   2: {
   3:     //其他成员    
   4:     public void EnableClientValidation();
   5:     public void EnableClientValidation(bool enabled);
   6:     public void EnableUnobtrusiveJavaScript();
   7:     public void EnableUnobtrusiveJavaScript(bool enabled);
   8:    
   9:     public static bool ClientValidationEnabled { get; set; }    
  10:     public static bool UnobtrusiveJavaScriptEnabled { get; set; }    
  11: }

接口IView的Render方法的第二个参数是一个TextWriter对象。对于该方法来说,只要我们将内容写入该TextWriter即完成了针对相关内容在View上的呈现,因为在调用Render方法的时候,作为该参数的是当前HttpResponse的Output属性表示的TextWriter。

二、ViewEngine

View引擎的核心是一个ViewEngine对象,它实现了IViewEngine接口。如下面的代码片断所示,IViewEngine定义了两个FindView和FindPartialView方法根据指定的Controller上下文、View名称和布局文件名称去获取对应的View和Partial View,两个方法中具有一个布尔类型的参数useCache表示是否启用缓存。另一个方法ReleaseView用于释放View对象。

代码语言:js
AI代码解释
复制
   1: public interface IViewEngine
   2: {    
   3:     ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
   4:     ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
   5:     void ReleaseView(ControllerContext controllerContext, IView view);
   6: }

FindView和FindPartialView方法返回的并不是实现了IView接口的类型的对象,而是一个类型为System.Web.Mvc.ViewEngineResult对象。如下面的代码片断所示,ViewEngineResult的只读属性View和ViewEngine属性表示找到的View对象和表示自身的ViewEngine对象。在成功获取到对应View的情况下这两个属性会通过构造函数进行初始化。如果没有找到相应的View,则将一个搜寻位置列表传入另一个构造函数创建一个ViewEngineResult,而只读属性SearchedLocations表示的就是这么一个搜寻位置列表。

代码语言:js
AI代码解释
复制
   1: public class ViewEngineResult
   2: {    
   3:     public ViewEngineResult(IEnumerable<string> searchedLocations);
   4:     public ViewEngineResult(IView view, IViewEngine viewEngine);
   5:    
   6:     public IEnumerable<string> SearchedLocations { get; }
   7:     public IView               View { get; }
   8:     public IViewEngine         ViewEngine { get; }
   9: }

如果返回的ViewEngineResult包含一个具体的View,那么这个View将会最终被呈现出来。反之,如果ViewEngineResult仅仅包含一个通过SearchedLocations属性表示的在获取目标View过程中使用的搜索位置列表,那么最终呈现出来的就是如下图所示的包含该列表的错误页面。

我们可以通过一个简单的实例来验证这一点。在通过Viual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们定义了如下一个HomeController。在默认的Action方法Index中,我们通过ViewEngines的静态只读属性Engines得到一个全局ViewEngine列表,并调用其FindView方法试图去寻找一个根本不存在View(“NonExistentView”)。最后我们将得到的ViewEngineResult对象的SearchedLocations属性表示的搜寻位置列表呈现出来。

代码语言:js
AI代码解释
复制
   1: public class HomeController : Controller
   2: {
   3:     public void Index()
   4:     {
   5:         ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, "NonExistentView", null);
   6:         foreach (string location in result.SearchedLocations)
   7:         {
   8:             Response.Write(location + "<br/>");
   9:         }
  10:     }
  11: }

运行我们的程序后表示在获取目标View中采用的搜寻位置列表会如下图所示的方式呈现出来,而这个列表与上图是完全一致的。

在上面实例演示中涉及到了一个重要的静态类型ViewEngines,它通过如下定义的只读属性Engines维护一个全局ViewEngine列表。从给出的定义可以看出,两个原生的ViewEngine在初始化的时候就被添加到了该列表中,它们的类型就是分别代表Web Form和Razor引擎的WebFormViewEngineRazorViewEngine如果我们创建了一个自定义View引擎,相应的ViewEngine也可以通过ViewEngines进行注册。

代码语言:js
AI代码解释
复制
   1: public static class ViewEngines
   2: {
   3:     private static readonly ViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine(), new RazorViewEngine() };
   4:    
   5:     public static ViewEngineCollection Engines
   6:     {
   7:         get { return _engines;}
   8:     }
   9: }
  10:  
  11: public class ViewEngineCollection : Collection<IViewEngine>
  12: {
  13:     //其他成员
  14:     public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName);
  15:     public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName);
  16: }

ViewEngines的静态只读属性Engines的类型是ViewEngineCollection,它是一个元素类型为IViewEngine的集合。ViewEngineCollection同样定义了FindView/FindPartialView这两个方法用于获取指定名称的View和分部View,在方法内部它会遍历集合中 的ViewEngine对象并调用它们的同名方法直到找的一个具体的View或者Partial View。由于WebFormViewEngine排在RazorViewEngine之前,所以前者会被优先使用,这可以从上面两张截图所示的搜寻位置列表看出来(先搜索.aspx和.ascx,再搜索.cshtml和.vbhtml)。

对于ViewEngineCollection的FindView/FindPartialView方法来说,不知道读者是否注意到了它们没有一个表示是否采用缓存的useCache参数。实际上当这两个方法被调用的时候,会先采用缓存的方式调用相应的ViewEngine,如果返回为Null,则以不采用缓存的方式再次调用它们。

三、ViewResult的执行

View引擎对View的获取以及对View的呈现最初是通过ViewResult触发的,那么两者是如何衔接的呢?这是本小节着重讨论的问题,在这之前我们不妨先来看看ViewResult的定义。如下面的代码片断所示,表示ViewResult的类型ViewResult是抽象类ViewResultBase的子类。

代码语言:js
AI代码解释
复制
   1: public class ViewResult : ViewResultBase
   2: {    
   3:     protected override ViewEngineResult FindView(ControllerContext context);
   4:     public string MasterName { get; set; }
   5: }
   6:  
   7: public abstract class ViewResultBase : ActionResult
   8: {   
   9:     public override void ExecuteResult(ControllerContext context);
  10:     protected abstract ViewEngineResult FindView(ControllerContext context);
  11:   
  12:     public object                     Model { get; }
  13:     public TempDataDictionary         TempData { get; set; }    
  14:     [Dynamic]
  15:     public object                     ViewBag { [return: Dynamic] get; }
  16:     public ViewDataDictionary         ViewData { get; set; }   
  17:     public string                     ViewName { get; set; }
  18:     public ViewEngineCollection       ViewEngineCollection { get; set; }
  19:     public IView                      View { get; set; }
  20: }

ViewResultBase的只读属性Model表示作为View的Model对象,三个表示数据状态的属性(ViewData、ViewBag和TempData)来源于Controller的同名属性。View和ViewName属性则是代表具体的View对象和View的名称。ViewEngineCollection属性值默认来源于ViewEngines的静态属性Engines代表的全局ViewEngine列表。

ViewResultBase用于获取具体View的FindView方法在ViewResult类中被实现,后者提供了额外的属性MasterName表示布局文件名称。在FindView方法的内部会直接调用ViewEngineCollection属性的FindView方法,如果返回的ViewEngineResult包含一个具体的View(View属性不为空),则直接返回该ViewEngineResult,否则抛出一个InvalidOperation异常,并将通过ViewEngineResult的SearchedLocations属性表示的搜寻位置列表格式化成一个字符串作为该异常的消息,所以图8-5所示的搜寻位置列表实际上是抛出的InvalidOperation异常的消息。

ASP.NET MVC的View引擎涉及到的相关的类型/接口以及它们之间的关系可以通过如图下所示的UML来表示。ViewResult通过静态类型ViewEngines利用View引擎激活对应的View对象并最终将View的内容呈现出来。

与除EmptyResult以外的所有ActionResult类型一样,抽象类Conrtoller中提供了相应的方法辅助创建ViewResult。如下面的代码片断所示,Controller具有如下一系列View方法帮助我们根据指定的View名称、View对象、布局文件名称和Model对象创建相应的ViewResult。

代码语言:js
AI代码解释
复制
   1: public abstract class Controller : ControllerBase, ...
   2: {
   3:     //其他成员   
   4:     protected ViewResult View();
   5:     protected ViewResult View(object model);
   6:     protected ViewResult View(string viewName);
   7:     protected ViewResult View(IView view);
   8:     protected ViewResult View(string viewName, object model);
   9:     protected ViewResult View(string viewName, string masterName);
  10:     protected virtual ViewResult View(IView view, object model);
  11:     protected virtual ViewResult View(string viewName, string masterName, object model);
  12: }

ViewResult与View引擎的交互体现在用于执行执行ActionView的ExecuteResult上。如下面的代码片断所示,如果View属性为Null,会调用FindView方法得到一个用于封装指定名称(如果没有执行则采用当前的Action名称作为View名称)的View的ViewEngineResult对象,并将其View属性作为自身的View。然后创建View上下文,并将该上下文和当前HttpResponse的Output属性代表的TextWriter对象作为参数调用View对象的Render方法实现对View的最终呈现。View呈现完成之后,通过ViewEngineResult得到对应的ViewEngine,并调用其Release对象对View进行回收操作。

代码语言:js
AI代码解释
复制
   1: public abstract class ViewResultBase : ActionResult
   2: {
   3:     //其他成员
   4:     public override void ExecuteResult(ControllerContext context)
   5:     {   
   6:         //其他操作     
   7:         if (string.IsNullOrEmpty(this.ViewName))
   8:         {
   9:             this.ViewName = context.RouteData.GetRequiredString("action");
  10:         }
  11:         ViewEngineResult result = null;
  12:         if (this.View == null)
  13:         {
  14:             result = this.FindView(context);
  15:             this.View = result.View;
  16:         }
  17:         TextWriter output = context.HttpContext.Response.Output;
  18:         ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output);
  19:         this.View.Render(viewContext, output);
  20:         if (result != null)
  21:         {
  22:             result.ViewEngine.ReleaseView(context, this.View);
  23:         }
  24:     }
  25: }

ViewResult为们提供了一种与View引擎交互的手段,其实在进行View的获取和呈现的时候完全可以抛开ViewResult,直接利用View引擎来完成,如下两种Action方法的定义是完全等效的。

代码语言:js
AI代码解释
复制
   1: //Action方法直接返回ViewResult
   2: public class HomeController : Controller
   3: {
   4:     public ActionResult Index()
   5:     {
   6:         return View();
   7:     }
   8: }
   9:  
  10: //Action方法直接调用View引擎
  11: public class HomeController : Controller
  12: {
  13:     public void Index()
  14:     {
  15:         string viewName = ControllerContext.RouteData.GetRequiredString("action");
  16:         ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, viewName, null);
  17:         if (null == result.View)
  18:         { 
  19:             throw new InvalidOperationException(FormatErrorMessage(viewName,result.SearchedLocations));
  20:         }
  21:         try
  22:         {
  23:             ViewContext viewContext = new ViewContext(ControllerContext, result.View, this.ViewData, this.TempData, Response.Output);
  24:             result.View.Render(viewContext, viewContext.Writer);
  25:         }
  26:         finally
  27:         {
  28:             result.ViewEngine.ReleaseView(ControllerContext, result.View);
  29:         }
  30:     }
  31:  
  32:     private string FormatErrorMessage(string viewName, IEnumerable<string> searchedLocations)
  33:     {
  34:         string format = "The view '{0}' or its master was not found or no view engine supports the searched locations. The following locations were searched:{1}";
  35:         StringBuilder builder = new StringBuilder();
  36:         foreach (string str in searchedLocations)
  37:         {
  38:             builder.AppendLine();
  39:             builder.Append(str);
  40:         }
  41:         return string.Format(CultureInfo.CurrentCulture, format, viewName, builder);
  42:     }
  43: }

上面我们仅仅介绍了ViewResult利用View引擎进行View的获取和呈现,其实当我们调用HtmlHelper的扩展方法Partial将指定的Partial View的HTML呈现出来时,内部调用View引擎的方式与之类

ASP.NET MVC的View是如何被呈现出来的?[设计篇] ASP.NET MVC的View是如何被呈现出来的?[实例篇]

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2012-08-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
使用Matplotlib对数据进行高级可视化(基本图,3D图和小部件)
可视化在当今世界许多领域的结果传播中发挥着重要作用。如果没有适当的可视化,很难揭示结果,理解变量之间的复杂关系并描述数据的趋势。
代码医生工作室
2019/06/21
4K0
使用Matplotlib对数据进行高级可视化(基本图,3D图和小部件)
深度好文 | Matplotlib 可视化最有价值的 50 个图表(附完整 Python 源代码)
在数据分析和可视化中最有用的 50 个 Matplotlib 图表。 这些图表列表允许您使用 python 的 matplotlib 和 seaborn 库选择要显示的可视化对象。
数据派THU
2019/05/09
1.8K0
深度好文 | Matplotlib 可视化最有价值的 50 个图表(附完整 Python 源代码)
深度学习基础之matplotlib,一文搞定各个示例
Matplotlib 是 Python 的绘图库。它可与 NumPy 一起使用 ,Matplotlib也是深度学习的常用绘图库,主要是将训练的成果进行图形化,因为这样更直观,更方便发现训练中的问题,今天来学习下,走起!!
香菜聊游戏
2021/10/19
1.7K0
深度学习基础之matplotlib,一文搞定各个示例
Python数据分析之matplotlib(应用篇)
matplotlib核心剖析(http://www.cnblogs.com/vamei/archive/2013/01/30/2879700.html#commentform)
AI异构
2020/07/29
3590
Python数据分析之matplotlib(应用篇)
绘图技巧 | 我总结了雷达图的绘制方法(R+Python)
今天给大家介绍的的图表为雷达图(Radar/Spider chart),这种类型图表在生活中较常使用,是一种以从同一点开始的轴上表示的三个或更多个定量变量的二维图表的形式显示多变量数据的图形方法。较常用的场景多为分析企业经营状况(收益性、生产性、流动性、安全性和成长性)。本期推文带你使R-Python绘制雷达图,主要内容如下:
DataCharm
2021/04/16
6.1K0
绘图技巧 | 我总结了雷达图的绘制方法(R+Python)
25 个常用 Matplotlib 图的 Python 代码,收藏收藏!
大家好,今天要分享给大家25个Matplotlib图的汇总,在数据分析和可视化中非常有用,文章较长,可以马起来慢慢练手。
Python数据科学
2020/05/26
8680
python matplotlib各种绘图类型完整总结
plot([x], y, [fmt], [x2], y2, [fmt2], …, **kwargs)
Twcat_tree
2022/12/05
6.2K0
python matplotlib各种绘图类型完整总结
数据分析最有用的Top 50 Matplotlib图(带有完整的Python代码)(上)
50个Matplotlib图的汇编,在数据分析和可视化中最有用。此列表允许您使用Python的Matplotlib和Seaborn库选择要显示的可视化对象。
Datawhale
2019/10/18
2.1K0
数据分析最有用的Top 50 Matplotlib图(带有完整的Python代码)(上)
利用Python绘制全国各省新型冠状病毒疫情变化动态图
题图:Image by enriquelopezgarre from Pixabay
猴哥yuri
2020/02/25
2.6K0
又再肝3天,整理了65个Matplotlib案例,这能不收藏?
Matplotlib 作为 Python 家族当中最为著名的画图工具,基本的操作还是要掌握的,今天就来分享一波
周萝卜
2021/11/08
2.6K0
matplotlib 3D 绘图(二)
四. 3D 散点图 from mpl_toolkits.mplot3d import Axes3D import matplotlib.pyplot as plt import numpy as np
用户6021899
2019/08/14
8290
Matplotlib 可视化最有价值的 14 个图表(附完整 Python 源代码)
这些图表根据可视化目标的7个不同情景进行分组。 例如,如果要想象两个变量之间的关系,请查看“关联”部分下的图表。 或者,如果您想要显示值如何随时间变化,请查看“变化”部分,依此类推。
CSDN技术头条
2019/11/19
1.2K0
Matplotlib 可视化最有价值的 14 个图表(附完整 Python 源代码)
Matplotlib 1.4W+字基础教程来了(收藏吃灰去吧~~)
参考:Rougier N P, Droettboom M, Bourne P E, et al. Ten Simple Rules for Better Figures[J]. PLOS Computational Biology【IF 4.7】, 2014, 10(9).感兴趣戳:https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4161295/pdf/pcbi.1003833.pdf
DataCharm
2021/02/22
1.6K0
Matplotlib 1.4W+字基础教程来了(收藏吃灰去吧~~)
Python matplotlib绘制雷达图
雷达图也被称为网络图,蜘蛛图,星图,蜘蛛网图,是一个不规则的多边形。雷达图可以形象地展示相同事物的多维指标,应用场景非常多。
Python碎片公众号
2021/02/26
3.2K0
Python matplotlib绘制雷达图
matplotlib进阶:Artist
FigureCanvas 和 Renderer 解决和用户界面(如 wxPython)或绘图语言(如 PostScript)间通信的所有细节。而Artists 解决figure,text,lines这些元素的呈现和布局相关的所有细节。通常95%的时间都会花在 Artists 上。
bugsuse
2020/04/21
1.6K0
matplotlib进阶:Artist
python下Matplotlib绘图案例与常见设置简介
首先一幅Matplotlib的图像组成部分介绍。 基本构成 在matplotlib中,整个图像为一个Figure对象。在Figure对象中可以包含一个或者多个Axes对象。每个Axes(ax)对象都是
学到老
2018/04/17
1.6K0
python下Matplotlib绘图案例与常见设置简介
matplotlib安装及使用
matplotlib是基于python语言的开源项目,旨在为python提供一个数据绘图包。我将在这篇文章中介绍matplotlib API的核心对象,并介绍如何使用这些对象来实现绘图。实际上,matplotlib的对象体系严谨而有趣,为使用者提供了巨大的发挥空间。用户在熟悉了核心对象之后,可以轻易的定制图像。matplotlib的对象体系也是计算机图形学的一个优秀范例。即使你不是python程序员,你也可以从文中了解一些通用的图形绘制原则。matplotlib使用numpy进行数组运算,并调用一系列其他的python库来实现硬件交互。matplotlib的核心是一套由对象构成的绘图API。
狼啸风云
2023/10/07
6490
matplotlib安装及使用
Matplotlib从入门到精通02-层次元素和容器
参考: https://datawhalechina.github.io/fantastic-matplotlib/%E7%AC%AC%E4%B8%80%E5%9B%9E%EF%BC%9AMatplotlib%E5%88%9D%E7%9B%B8%E8%AF%86/index.html
IT从业者张某某
2023/10/16
6370
Matplotlib从入门到精通02-层次元素和容器
Matplotlib_Study01
from matplotlib.font_manager import FontProperties
Echo_Wish
2023/11/30
3050
Matplotlib_Study01
推荐阅读
相关推荐
使用Matplotlib对数据进行高级可视化(基本图,3D图和小部件)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档