设计并非外观怎样,感觉如何。设计是(解决)如何工作的问题 –Steve Jobs
视图引擎的结构与性能 视图引擎是位浏览器生成html输出的组件
控制器—数据—>视图模板—–>html
视图引擎机制 在asp.net mvc中,视图引擎只是一个实现固定接口(IViewEngine
)的类。
每个应用程序可以有一个或多个视图引擎。asp.net mvc5中默认两个Razor,Aspx
视图引擎类(检测已注册的试图引擎) ViewEngines
类跟踪当前已安装的引擎的系统资源库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 namespace System.Web.Mvc { public static class ViewEngines { private static readonly ViewEngineCollection _engines; public static ViewEngineCollection Engines { get { return ViewEngines._engines; } } static ViewEngines ( ) { ViewEngineCollection engineCollection = new ViewEngineCollection(); engineCollection.Add((IViewEngine) new WebFormViewEngine()); engineCollection.Add((IViewEngine) new RazorViewEngine()); ViewEngines._engines = engineCollection; } } }
引擎集合_engines
中初始化两个默认视图引擎
解析视图引擎 视图引擎是实现IViewEngine
接口的类,引擎会以架构的名义检索视图对象(实际响应的所需所有信息的容器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public interface IViewEngine { ViewEngineResult FindPartialView (ControllerContext controllerContext, string partialViewName, bool useCache ) ; ViewEngineResult FindView (ControllerContext controllerContext, string viewName, string masterName, bool useCache ) ; void ReleaseView (ControllerContext controllerContext, IView view ) ; }
视图引擎findview
返回的ViewEngineResult
包含三个元素:视图对象,对应的视图引擎,视图模板位置列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class ViewEngineResult { 。。。 public IEnumerable<string > SearchedLocations { get ; private set ; } public IView View { get ; private set ; } public IViewEngine ViewEngine { get ; private set ; } }
视图引擎调用者是谁 controller与viewengine一起工作明显是由mvc框架实现的,更细一步,控制器和视图引擎的活动是由一个外部管理器对象(操作调用程序)进行协调的。
操作调用程序由负责处理HTTP处理程序直接触发。
操作调用程序主要做两件事:1. 执行控制器的方法并保存操作结果 2. 处理操作结果。
1 2 3 4 5 6 7 8 9 10 ViewResult:ViewResultBase public ViewResult ( ) ;public string MasterName { get ; set ; }protected override ViewEngineResult FindView (ControllerContext context ) ;ViewResultBase:ActionResult protected abstract ViewEngineResult FindView (ControllerContext context ) ;public override void ExecuteResult (ControllerContext context ) ;
操作调用程序调用ExecuteResult
1 2 3 4 5 6 7 8 9 10 11 public override void ExecuteResult(ControllerContext context){ 。。。 ViewEngineResult result = null ; if (this .View == null ) { result = this .FindView(context); this .View = result.View; } 。。。 }
ExecuteResult
会调用FindView
1 2 3 4 5 6 7 8 9 10 11 12 13 14 protected override ViewEngineResult FindView (ControllerContext context ) { ViewEngineResult result = base .ViewEngineCollection.FindView(context, base .ViewName, this .MasterName); if (result.View != null ) { return result; } StringBuilder builder = new StringBuilder(); foreach (string str in result.SearchedLocations) { builder.AppendLine(); builder.Append(str); } throw new InvalidOperationException(string .Format(CultureInfo.CurrentCulture, MvcResources.Common_ViewNotFound, new object [] { base .ViewName, builder })); }
FindView
此时会调用ViewEngine
里面的方法,遍历模板列表,没有抛出包含所有列表的信息的异常
ExecuteResult
调用完FindView
,执行this.View.Render(viewContext, output);
将结果输出到响应流,这里viewContext
中= new ViewContext(context, this.View, this.ViewData, this.TempData, output);
包含了传入view里面的viewdata
,同时强类型模型也是通过viewdata
传入的
1 2 3 4 5 6 7 public TModel Model { get { return this .ViewData.Model; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public override void ExecuteResult(ControllerContext context ) { if (context == null ) { throw new ArgumentNullException("context" ); } if (string .IsNullOrEmpty (this .ViewName )) { this .ViewName = context .RouteData .GetRequiredString ("action" ); } ViewEngineResult result = null ; if (this .View == null ) { result = this .FindView (context ); this .View = result.View ; } TextWriter output = context .HttpContext .Response .Output ; ViewContext viewContext = new ViewContext(context , this .View , this .ViewData , this .TempData , output ); this .View .Render (viewContext, output ); if (result != null ) { result.ViewEngine .ReleaseView (context , this .View ); } }
视图对象View public IView View { get; [CompilerGenerated] private set; }
视图对象是实现IView接口的实例,视图对象的唯一目的是编写一些HTML响应到文本编辑器
视图模板定义 模板解析 在任务处理结束时,控制器要找出呈现给用户的下一个视图的名称。但视图名称转换成对应的HTML还需要一些额外的步骤
1。确定使用哪个视图引擎可以成功处理该视图的请求 2。视图名称必须匹配到一个html布局,并基于该布局创建一个试图对象 3。从获取视图名称开始,ViewResult对象就会按照视图引擎出现在ViewEngines.Engines集合中顺序,对所有已经安装的视图引擎进行插叙
默认规则和文件夹 默认情况下系统会在View文件夹下找,controller名对应的文件夹下action对应的后缀为cshtml、vbhtml的文件(Razor)
用于视图的模板 aspx视图引擎与webform中aspx页面语法基本一致,最大的不同在于,mvc中aspx并不是公共资源,不通i过特殊设置不能直接访问,所有的请求都是有controller接受和回复,对应的aspx页面只不过是回复的模板
1 2 3 4 5 6 public ActionResult About(){ ViewBag.Message = "Your application description page." ; return View () ; }
About.cshtml,这里通过viewbag传递的值
1 2 3 4 5 6 7 @{ ViewBag.Title = "About" ; } <h2> @ViewBag.Title.</h2> <h3> @ViewBag.Message</h3> <p> Use this area to provide additional information.</p>
母版视图
在 cshtml中直接指定
1 2 3 4 @{ Layout = "Index.cshtml" ViewBag.Title = "About" }
默认的layout在_ViewStart.cshtml
中指定
HTML帮助器 asp.net mvc中创建html视图使用的不是webform中的空间而是HTML帮助器。
HTML帮助器就是一个简单的HTML工厂,返回的是没mvchtmlstring,内部实现也是使用stringbuilder进行拼接,视图引擎解析
基础帮助器 BeginForm,BeginRouteForm
EndForm
CheckBox,CheckBoxFor
Hidden,HiddenFor
Password,PasswordFor
….
用法是@Html.ActionLink(...)
BeginForm,BeginRouteForm
区别是带Route可以导航到任何已注册路由的任何位置,BeginForm只能使用控制器,action等属性
CheckBox,CheckBoxFor
区别:带For参数只接受lamada表达式
注意:HTML帮助器是视图引擎的内置属性,所以不同的视图引擎帮助器是不同的,尽管默认的两个细节都是差不多的,但内部还是有些不同的
下面举一些例子
呈现HTML表单 1 2 3 4 @using (Html.BeginForm("Index" , "Home" )) { }
BeginForm代表《form》,using中finally会添加《/form》,BeginForm当然还有很多重载
呈现输入元素 再次说明,从功能的角度使用帮助器和直接编写HTML是一样的效果,下面的事例是复选框选中同时禁用
1 @Html .CheckBox("wode" , true , htmlAttributes: new { disabled = "disabled" })
操作链接 1 @Html .ActionLink ("index ","Index ")
对应的html
RouteLink帮助器的工作方式差不多相同,可以使用任何已经注册的路由名称来确定生成的URL模式
1 @Html .RouteLink("index" , "Default" , new { Controller = "Home" , Action = "Index" })
如果想在a标签下嵌套其他标签,这时候可以使用UrlHelper来解决
1 2 3 <a href="@Url.Action(" Index")" > </a >
部分视图 可以使用Partial和RenderPartial帮助器插入部分视图看。区别是,Partial返回的是字符串,RenderPartial会写入输出流,返回空,所以两者的用法略有不同
1 2 @Html .Partial ("_PartialForTest ") @{ Html .RenderPartial ("_PartialForTest ");}
HtmlHelper其他的原生方法
AntiForgeryToken
:创建一个包含口令的隐藏域,Post、Get到controller时添加[ValidateAntiForgeryToken]特性验证
Raw
:返回未经编码的原始Html字符串
AttributeEncode
:这个方法在效率上比HtmlEncode快很多。但这是有代价的付出的HtmlAttributeEncode只对 左引号(“ ),左括号(( ),and符合(&)转换为等效的字符实体。
“*HtmlAttributeEncode 方法的结果字符串只应当用于由双引号括起的属性。使用 HtmlAttributeEncode* 方法和用单引号括起的属性可能会引起安全问题。”
看大家的选择了,如果仅仅是担心输出HTML代码和JS方面的安全,可以使用HtmlAttributeEncode 方法,效率更高!
1 2 @Html.AttributeEncode("<script>alert(\"nihao\")</script>" ) html: < ;script>alert(" ;nihao" ;)< ;/script>
模板化帮助器 一遍遍编写Html模板枯燥且容易出错,模板化帮助器可以很好的解决这一问题
在asp.net mvc中Editor,Display是两个基本的模板化帮助器
Display帮助器 显示viewdata里面的值
1 2 3 4 5 @{ ViewData["Title" ] = "nihao" ; } @Html .Display ("Title" )
显示model对应属性的值
1 @Html .DisplayFor (model => model.Title)
显示model中所有的值
Editor帮助器 Editor帮助器让你可以编辑指定的值
1 @Html .EditorFor (model => model.Title)
模板化帮助器可以使用模板进行自定义编辑,比如对于Editor可以将模板文件放在对应的controller下面的EditorTemplates下或者公用的Views/Shared下
1 @Html .EditorFor (model => model.date,"DateTime" )
视图引擎会自己找对应文件夹下面的文件
自定义帮助器 有时候mvc提供的帮助器下没有我们需要的,这时候可以编辑自定义帮助器
帮助器结构 htmlhelper就是一个普通拓展方法
MvcHtmlString不只是字符串 自定义帮助器返回对象最好是MvcHtmlString封装对象而不是普通的字符串,实际上,所有的原生的帮助器方法也都是通过stringbuilder然后构造的MvcHtmlString对象,可以通过以下代码轻松的创建MvcHtmlString对象
1 MvcHtmlString.Create ((string )str);
自定义帮助器样例 添加引用并使用
1 2 3 @using _02.Helper@Html .Title("自定义帮助器创建的title" )
简单的拓展方法
1 2 3 4 5 6 7 8 9 10 11 12 13 public static class TitleHelper { public static MvcHtmlString Title (this HtmlHelper html, string title, string className ) { var h1Tag = new TagBuilder("h1" ); if (!string .IsNullOrEmpty(className)) { h1Tag.Attributes["class" ] = className; } h1Tag.SetInnerText(title); return MvcHtmlString.Create(h1Tag.ToString()); } }
Razor视图引擎 视图引擎内部机制 搜索位置 视图引擎的构造函数中定义了视图搜索位置的格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public RazorViewEngine (IViewPageActivator viewPageActivator ) : base (viewPageActivator ) { this .AreaViewLocationFormats = new string [4 ] { "~/Areas/{2}/Views/{1}/{0}.cshtml" , "~/Areas/{2}/Views/{1}/{0}.vbhtml" , "~/Areas/{2}/Views/Shared/{0}.cshtml" , "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; this .AreaMasterLocationFormats = new string [4 ] { "~/Areas/{2}/Views/{1}/{0}.cshtml" , "~/Areas/{2}/Views/{1}/{0}.vbhtml" , "~/Areas/{2}/Views/Shared/{0}.cshtml" , "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; this .AreaPartialViewLocationFormats = new string [4 ] { "~/Areas/{2}/Views/{1}/{0}.cshtml" , "~/Areas/{2}/Views/{1}/{0}.vbhtml" , "~/Areas/{2}/Views/Shared/{0}.cshtml" , "~/Areas/{2}/Views/Shared/{0}.vbhtml" }; this .ViewLocationFormats = new string [4 ] { "~/Views/{1}/{0}.cshtml" , "~/Views/{1}/{0}.vbhtml" , "~/Views/Shared/{0}.cshtml" , "~/Views/Shared/{0}.vbhtml" }; this .MasterLocationFormats = new string [4 ] { "~/Views/{1}/{0}.cshtml" , "~/Views/{1}/{0}.vbhtml" , "~/Views/Shared/{0}.cshtml" , "~/Views/Shared/{0}.vbhtml" }; this .PartialViewLocationFormats = new string [4 ] { "~/Views/{1}/{0}.cshtml" , "~/Views/{1}/{0}.vbhtml" , "~/Views/Shared/{0}.cshtml" , "~/Views/Shared/{0}.vbhtml" }; this .FileExtensions = new string [2 ] { "cshtml" , "vbhtml" }; }
代码碎块
解析器会正确的识别标记 @{<h1>@Viewbag.Title</h1>}
可以通过@()插入字符
1 <p> @("hello" + Model.userName)</p>
括号中同时可以 放置一个函数
1 2 <p>@(Method () )</p > //等同于<p >@</p >
注释使用@。。。 @,使用@@表示@字符
Razor处理过的视图都是自动编码的,如果想保留原始格式使用
条件式代码碎块 1 2 3 <div class ="@Model.Css" > .... </div >
当Model.Css为null时,解析器理论上会抛出异常,但mvc4之后,条件逻辑已经内置在Razor引擎中了
Razor视图对象 Razor引擎生成视图对象可以通过反射dll看到继承自WebViewPage
1 public class _Page_Views_Home_About_cshtml : WebViewPage <object>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 namespace System.Web.Mvc { using System; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; using System.Web; using System.Web.Mvc.Properties; using System.Web.WebPages; public abstract class WebViewPage : WebPageBase , IViewDataContainer , IViewStartPageChild { private AjaxHelper<object > _ajax; private HttpContextBase _context; private DynamicViewDataDictionary _dynamicViewData; private HtmlHelper<object > _html; private ViewDataDictionary _viewData; protected WebViewPage() { } protected override void ConfigurePage(WebPageBase parentPage) { WebViewPage page = parentPage as WebViewPage; if (page == null ) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, new object [] { parentPage.get_VirtualPath() })); } this .ViewContext = page.ViewContext; this .ViewData = page.ViewData; this .InitHelpers(); } public override void ExecutePageHierarchy() { TextWriter writer = this .ViewContext.Writer; this .ViewContext.Writer = base.get_Output(); base.ExecutePageHierarchy(); if (!string.IsNullOrEmpty(this .OverridenLayoutPath)) { this .set_Layout(this .OverridenLayoutPath); } this .ViewContext.Writer = writer; } public virtual void InitHelpers() { this .Html = null ; this .Ajax = null ; this .Url = new UrlHelper(this .ViewContext.RequestContext); } protected virtual void SetViewData(ViewDataDictionary viewData) { this ._viewData = viewData; } public AjaxHelper<object > Ajax { get { if ((this ._ajax == null ) && (this .ViewContext != null )) { this ._ajax = new AjaxHelper<object >(this .ViewContext, this ); } return this ._ajax; } set { this ._ajax = value; } } public override HttpContextBase Context { get => (this ._context ?? this .ViewContext.HttpContext); set { this ._context = value; } } public HtmlHelper<object > Html { get { if ((this ._html == null ) && (this .ViewContext != null )) { this ._html = new HtmlHelper<object >(this .ViewContext, this ); } return this ._html; } set { this ._html = value; } } public object Model => this .ViewData.Model; internal string OverridenLayoutPath { get ; set ; } public TempDataDictionary TempData => this .ViewContext.TempData; public UrlHelper Url { get ; set ; } [Dynamic] public object ViewBag { [return : Dynamic] get { Func<ViewDataDictionary> viewDataThunk = null ; if (this ._dynamicViewData == null ) { if (viewDataThunk == null ) { viewDataThunk = () => this .ViewData; } this ._dynamicViewData = new DynamicViewDataDictionary(viewDataThunk); } return this ._dynamicViewData; } } public System.Web.Mvc.ViewContext ViewContext { get ; set ; } public ViewDataDictionary ViewData { get { if (this ._viewData == null ) { this .SetViewData(new ViewDataDictionary()); } return this ._viewData; } set { this .SetViewData(value); } } } }
可以看到View可以使用的对象都在这里面
设计一个样例视图 建立视图专用的Model 1 2 3 4 5 6 7 namespace _02 .Models .Home { public class HomeIndexViewModel { public string Str { get ; set ; } } }
在视图中声明model类型
1 @model _02.Models .Home .HomeIndexViewModel
使用Model
1 2 3 <div > <h1 > @Model.Str</h1 > </div >
controller中添加返回对象
1 2 3 4 5 6 7 public ActionResult Index() { return View(new HomeIndexViewModel() { Str = "传递的数据" }); }
定义母版视图 布局页就是一个普通的cshtml
页面但是必须添加@RenderBody()
告诉解析器在哪儿注入布局模板
layout可以使任意路径或设置条件,默认的layout在_VIewStart.cshtml
中定义
1 2 3 4 5 6 if (Request.Browser.IsMobileDevice) { Layout = "About.cshtml" } }
通过~返回根目录
1 Layout = "~/Views/Shared/_Layout.cshtml"
定义节 RenderBody
方法定义了布局注入的单个点,但你可能需要将内容注入到多个位置,在布局模板中,可以通过在希望出现内容的位置放置一个RenderSection
的调用来定义注入点
默认的_Layout中就有关于scripts的节
1 @RenderSection ("scripts" , required: false )
required: false指的是不是必须的,可以通过@section+名称进行节的编写
1 2 3 4 @section scripts { <script > alert("nihao" ); </script > }
节的默认内容 webform
中视图引擎中的母版页可以指定默认内容,Razor没有但是可以通过IsSectionDefined
进行判断,添加默认的内容
1 2 3 4 5 6 7 <div id ="footer" > @if (IsSectionDefined("name" )){ @RenderSection("name" ); }else { <span>undefined</span> } </div >
嵌套布局 布局是可以嵌套的通过指定Layout实现嵌套
声明式Html帮助器 通过HtmlHelper
创建网页内容如果有很多元素就十分困难,这时候使用声明式Html帮助器就方便很多
首先在项目中添加App_Code,添加MyHelpers.cshtml
,定义一个简单的方法,格式@helper+签名
1 2 3 4 @helper ShowTitle(string title) { <h1 >@title </h1 > }
使用
1 @MyHelpers .ShowTitle ("nihao ")
注意在App_Code外编辑的帮助器不会被检测
视图编码 视图建模 ViewData,ViewBag
通过这两个对象可以从controller中传值到view中,两个类型都是dynomic
,用法:
1 2 3 4 5 6 ViewData["Title" ]="nihao" ; ViewBag.Title ="nihao" ; view 中:ViewBag.Title
强类型视图模型 上面已经说过
应尽量减少viewbag,viewdata
,因为使用viewmodel
更易维护,同时注意的是这里的model是视图模型,应尽量减少直接将数据模型当做视图模型
高级功能 更改视图引擎检索格式 创建自己的检索格式,项目中只是用Razor就没必要检索aspx格式的视图,提高效率,同时可以将PartialViews
放在不同的地方便于管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 namespace _02.App_Start{ public class NewFormatViewEngine : RazorViewEngine { public NewFormatViewEngine () { this .MasterLocationFormats = base.MasterLocationFormats; this .ViewLocationFormats = new string [] { "~/Views/{1}/{0}.cshtml" }; this .PartialViewLocationFormats = new string [] { "~/PartialViews/{1}/{0}.cshtml" }; } } }
我在App_Start中创建自己的新类继承自RazorViewEngine
在Global.asax
中,清空原来的引擎,添加新的
1 2 3 4 5 6 7 8 9 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start () { 。。。 ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new NewFormatViewEngine()); } }
需要注意的是要添加Web.config配置对应的视图引擎,这里直接复制Views里面的即可
已经可以使用了,文件夹PartialViews ==> Home ==> Home_Contact_PartialView.cshtml
1 @Html .Partial ("Home_Contact_PartialView ")
呈现操作 当需要呈现一个固定不变的视图或者需要控制器提供数据的视图的时候使用呈现操作即可
菜单例子:控制中添加返回PartialView
的方法Menu
1 2 3 4 5 6 7 8 9 public ActionResult Menu () { return PartialView(new List<string >() { "menu1" , "menu2" , "menu3" }); }
创建对应的视图
1 2 3 4 5 6 7 8 @model IList<string> <div> @foreach (var str in Model) { <a> @str</a> } </div>
使用
与@Html.Partial一样ActionRender
也是写入到响应流,Action直接返回html字符串