MVC3 - 视图
摘要:
- 视图的作用
- 如何指定视图
- 强类型视图
- 理解视图模型
- 如何添加视图
- Razor的使用方法
- 如何指定分部视图
- 理解视图引擎
1.视图的作用
在ASP.NET MVC中向用户提供用户界面的过程由两部分组成
- 检查由控制器提交的ViewDataDictionary(通过ViewData属性访问)
- 将其内容转换为HTML格式
并非所有的视图都将渲染HTML格式,视图也可以渲染其它类型的内容。
从ASP.NET MVC3开始,视图数据可以通过ViewBag属性访问。ViewBag属性是动态的,可以通过ViewData属性访问的相同数据。
ViewBag.Message等同于ViewData["Message"],但彼此之间并不存在真正的技术优势。
但二者差异:
- 当要访问的关键字是一个有效的C#标识符时,ViewBag才起作用。如,在ViewData["Key With Spaces"]中存放一个值,那么将不能使用ViewBag访问这个值
- 这个动态的值不能作为一个参数传递给扩展的方法。因为编译器为了选择正确的扩展方法,在编译时必须知道每一个参数的真正类型。如,@Html.TextBox("name",ViewBag.Name)将编译失败,需显式转换
<body> <h1>@ViewBag.Message</h1> <p>...</p> </body>
视图并不像ASP.NET Web Forms和PHP,它本身不会被直接访问,浏览器不能指向一个视图并渲染它。
然而,视图总是被一个控制器渲染,该控制器向它提供了要渲染的数据。
2.指定视图
- 每个控制器在Views目录下都有一个对应的文件夹,其名称与控制器一样。
- 每一个控制器的View文件夹中,每一个操作方法都有一个名称相同的视图文件与之相对应。操作方法通过View方法返回ViewResult对象。如,
-
return View();
- 当控制器操作没有指定视图的名称时,操作方法返回的ViewResult将按照约定来确定视图(先在目录/Views/ControllerName下查找与控制器名称(不带Controller)相同的视图)。与APS.NET MVC的大部分约定设置一样,这一约定是可以重写的,指定不同视图(可以指定完全位于不同目录结构的视图)如,
-
return View("NotIndex"); return View("~/View/Example/Index.cshtml");//避开视图引擎的内部查找机制,需使用文件扩展名
3.强类型视图
创建Album实例列表并在视图中迭代它们:
public ActionResult List() { var albums = new List<Albums>(); for(int i = 0;i<10;i++) { albums.Add(new Albums{Title = "Product" + i }); } ViewBag.Albums = albums; return View(); } //视图 <ul> @foreach(Album a in (ViewBag.Albums as IEnumerable<Album>)) { <li>@a.Title</li> } </ul>
在枚举之前需将动态的ViewBag.Albums转换为IEnumerable<Album>类型,也可以使用dynamic使代码更整洁,但这样不能使用代码智能感知
<ul> @foreach(dynamic p in ViewBag.Albums) { <li>@p.Title</li> } </ul>
在Controller方法中,可以通过向重载的View方法中传递模型实例来指定模型,在后台传进View方法的值将赋值给ViewData.Model属性,如
return View(albums);
接下来,告知视图哪种类型的模式正在使用@model声明,注意需输入完全限定类型名,如
@model IEnumerable<MvcApplication1.Models.Album> <ul> @foreach(Album p in Model){ <li>@p.Title</li> </ul>
如果不像输入模型类型的完全限定类型名,可以使用@using关键字声明,如
@using MvcApplication1.Models
@model IEnumerable<Album>
对于在视图中经常使用的名称空间,比较好的方法是在Views目录下的web.config文件中声明,如
@using MvcApplication1.Models <system.web.webPages.razor> ... <pages pageBaseType="System.Web.Mvc.WebViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> ... <add namespace="MvcApplication1.Models /> ... </namespaces> <pages> </system.web.webPages.razor>
4.视图模型
视图通常需要显示没有直接映射到域模型的数据,当需要显示与视图主模型无关的额外数据时:
- 把数据存放ViewBag属性(优点:方便使用 缺点:如果严格控制流进视图的数据,就必须使所有类型都是强数据的,以便利用智能感知)
- 编写自定义的视图模型类
如创建购物车汇总页面:
public class ShoppingCartViewModel{ public IEnumerable<Product> Products{get;set;} public decimal CartTotal{get;set;} public string Message{get;set;} } //输入视图 @model ShoppingCartSummaryViewModel
5.添加视图及自定义T4视图模板(后者需上机测试,不理解)
6.Razor视图引擎
Razor提供了一个干净、轻量级、简单的视图引擎,不包含原有的Web Forms视图引擎中包含的“语法累赘”。最大限度地减少了语法和额外的字符。
代码表达式:
Razor中的核心转换字符是@,用作标记-代码的转换字符,有时也反过来用作代码-标记的转换字符。这里一共有两种基本类型的转换:代码表达式和代码块
WebForm视图只支持显式代码表达式,而ASP.NET MVC支持隐式代码表达式求解。
<h1>Listing @stuff.Length items.</h1>//ASP.NET MVC <h1>Listing <%:@stuff.Length %>items.</h1>//WEB Form
Razor十分智能,可以知道表达式后面的空格不是一个有效的标识符,所以可以顺畅地转回到标记语言。
下面的情况也可以准确的推断,自动从代码转回标记:
<ul> @foreach(var item in items){ <li>The item name is @item.</li>//Razor可以知道@item后的字符不是引用属性或方法,因为它看到在.后是尖括号 } </ul>
但下面的情况会出现潜在的二义性,想要的输出结果是<span>MyApp.Models</span>,但系统会提示string没有Models属性:
@{ string rootNamespace = "MyApp"; } <span>@rootNamespace.Models</span>
解决这种问题可以将表达式用圆括号括起来以支持显式代码表达式:
<span>@(rootNamespace).Models</span>
Razor足够智能,可以辨别出电子地址的一般模式,而不会处理这种形式的表达式。(内部采用了一个简单的算法,即采用@@符号转义一个@符号)
但当遇到向将这种形式字符串作为表达式时,需用圆括号指明,如
<li>Item_@item.Length</li>//这种情况下会匹配成邮件地址 <li>Item_@(item.Length)</li>//解决
当需要显示一些以@符号开头的语句时,Razor会先尝试解析这些隐式代码表达式,失败后使用@@来转义@符号
Html编码:
Razor表达式时用Html编码的
如果想展示Html标记,就返回一个System.Web.IHtmlString对象的实例,Razor并不对它进行编码。也可以创建一个HtmlString实例或者使用Html.Raw便捷方法。如
//将不会弹出警告,而会显示出编码信息 @{ string message = "<script>alert('haacked!');</script>"; } <span>@message</span> //将显示不经过Html编码的消息 @{ string message = "<strong>This is bold!</strong>"; } <span>Html.Raw(@message)</span>
但在JS中,仅仅是HTML编码是不够的,还需要使用@Ajax.JavaScriptStringEncode方法对用户输入进行编码,避免XSS攻击。如
<script> $(function(){ var message = 'Hello @ViewBag.Username'; ... </script> //需修改为 <script> $(function(){ var message = 'Hello @Ajax.JavaScriptStringEncode(@ViewBag.Username)'); ... </script>
代码块
Razor理解HTML标记语言的结构,可以自动转换代码。但相比之下,WebForm视图引擎就需要显式支出代码和标记之间转换的代码。如
@foreach(var item in stuff){ <li>The item name is @item.</li> } //Web Form视图引擎 <% foreach(var item in stuff){ %> <li>The item name is <%: item %>.</li> <% } %>
代码块除了需要@符号分割之外还需要使用花括号,如
@{ string是= “...”; ViewBag.Title " Another line of code"; } //或 @{Html.RenderPartial("SomePartial");}
注意:代码块中的语句(如foreach循环和if代码块中的语句)是不需要使用花括号的。
Razor语法示例
a.隐式代码表达式:Razor中的隐式代码表达式总是采用HTML编码方式。
//Razor <span>@model.Message</span> //Web Forms <span><%:model.Message %></span>
b.显示代码表达式:
//Razor <span>ISBN@(isbn)</span> //Web Forms <span>ISBN<%: isbn %></span
c.无编码代码表达式:
//Razor <span>@Html.Raw(model.Message)</span> //Web Forms <span><%:Html.Raw(model.Message) %></span>
d.代码块:
不像代码表达式先求得表达式的值,然后再输出到响应,代码块是简单的执行代码的部分,对于声明以后要使用的变量是有用的
//Razor @{ int x = 123; string y = "because"; } //Web Forms <% int x = 123; string y = "because"; %>
e.文本和标记相结合:
//Razor @foreach(var item in items){ <span>The item name is @item.Name.</span> } //Web Form <% foreach(var item in items){ %> <span>Item <%: item.Name %>.</span> <% } %>
f.混合代码和纯文本:Razor查找标签的开始位置以确定何时将代码转换为标记
//Razor @if(showMessage){ <text>This is plain text</text> } 或 @if(showMessage){ @:This is plain text. } //Web Form <% if(showMessage) {%> This is plain text. <% } %>
g.转义代码分隔符:可以用“@@”来编码“@”达到显示的目的。
//Razor My Twitter Handle is @hacked 或 My Twitter Handle is @@hacked //Web Form <% express %>marks a code nugget.
h.服务器端的注释:
//Razor @* This is a multiline server side comment. @if(showMessage){ <h1>@ViewBag.Message</h1> } ALL of this is commented out. *@ //Web Form <%-- This is a multiline server side comment. <% if(showMessage) {%> <h1><%: ViewBag.Message %></h1> <% } %> All of this is commented out. --%>
i.调用泛型方法:需整个表达式用圆括号括起来,因为调用泛型方法的代码包含尖括号,尖括号会导致Razor转回标记
//Razor @(Html.SomeMethod<AType>()) //Web Form <%: Html.SomeMethod<AType>() %>
布局
Razor的布局有助于应用程序中的多个视图保持一致的外观。
可以使用布局为网站定义公共模板,公共模板包含一个或多个占位符,应用程序中的其他视图为它们提供内容。
<!DOCTYPE html> //视图 <html> <head><title>@ViewBag.Title</title></head> <body> <h1>@ViewBag.Title</h1> <div id="main-content">@RenderBody()</div> <footer>@RenderSection("Footer")</footer> </body> </html> //定义 @{ Layout = "~</Views/Shared/SiteLayout.cshtml"; View.Title = "The Index!"; } <p>This is the main content!</p> @section Footer{ This is the <strong>footer</strong> }
渲染出的内容:
<!DOCTYPE html> <html> <head><title>The Index!</title></head> <body> <h1>The Index!</h1> <div id="main-content"><p>This is the main content!></p></div> <footer>This is the <strong> footer</strong></footer> </body> </html>
RenderSection方法有一个重载版本,运行指定不需要的节。
<footer>@RenderSection("Footer",false)</footer>
也可以通过以下方式来定义默认值
<footer> @if(IsSectionDefined("Footer")) { RenderSection("Footer"); } else{ <span>This is the default footer.</span> } </footer>
ViewStart
消除冗余的视图布局指定代码。
_ViewStart.cshtml的文件代码先于任何视图运行,所以一个视图可以重写Layout属性的默认值,从而重新选择一个不同的布局。
但如果一组视图拥有共同的设置,那么可以在这个文件中统一设置。
7.指定分部视图
操作方法可以通过PartialView方法以PartialViewResult的形式返回分部视图。如,
return PartialView();
但如果布局是由_ViewStart.cshtml页面指定(而不是直接在视图中)的,那么布局将无法渲染。
分部分视图在使用AJAX技术进行部分更新的情形中是有用的。
8.视图引擎
ASP.NET MVC的生命周期
HTTP请求—路由—控制器—视图结果—视图引擎—视图—响应
9.配置视图引擎
视图引擎配置在文件Global.asax.cs
在Application_Start方法中编写代码替代默认视图引擎
protected void Application_Start(){ ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new MyViewEngine()); RegisterRoutes(RouteTable.Routes); }
创建自定义视图引擎时,需要实现IViewEngine接口和IView接口(此部分内容较多,参考59~63页)