代码块
Razor 在视图中除了支持代码表达式以外, 还支持代码块。 回顾以前的代码:
@foreach(var item in stuff){
<li>The item name is @item.</li>
}
这段代码迭代了一个数组, 并为数组中的每一项显示了一个列表项元素。
这个语句有趣的地方是, foreach语句会自动转换带有起始<li>标签的标记。当看到这段代码时,人们有时可能会假定是换行符导致了这个转换的发生, 但是下面有效的代码片段证实了情况并非如此:
@foreach(var item in stuff){ <li>The item name is @item.</li> }
因为Razor理解HTML标记语言的结构,所以当<li>标签关闭时它也可以自动地转回代码。因此,这里不需要划定右花括号。
相比之下, 对于同样用于代码和标记之间转换的代码,Web Forms 视图引擎就不得不显式地指出, 如下所示:
<% foreach(var item in stuff) { %>
<li>The item name is <%: item %>.</li>
<%}%>
代码块除了需要@符号分割之外还需要使用花括号。 下面是一个多行代码块的例子:
@{
string s = "One line of code.";
ViewBag.Title = "Another line of code";
}
另外一个例子是当调用没有返回值 的方法(也就是返回类型为 void)时:
@{ Html.RenderPartial( "SomePartial" ); }
注意代码块中的语句(比如 foreach 循环和if 代码块中的语句)是不需要使用花括号的。因为Razor引擎有这些C#关键字的专门知识。
下节将对简洁Razor语法进行快速介绍,并展示各种Razor语法及其与Web Forms的对比。
Razor语法示例
1. 隐式代码表达式
如前所述, 代码表达式将被计算并将值写入到响应中, 这就是在视图中显示值的一般原理。
<span>@model.Message</span>
2. 显示代码表达式
代码表达式的值将被计算并写入到响应中, 这就是视图中显示值的一般原理。
<span> 1+2 =@(1+2) </span>
3. 无编码代码表达式
有些情况下, 需要显示地渲染一些不应该采用HTML编码的值,这时可以采用Html.Raw方法来保证该值不被编码。
<span>@Html.Raw(model.Message)</span>
4. 代码块
不像代码表达式先求得表达式的值,然后再输出到响应,代码块是简单地执行代码部分。这一点对于声明以后要使用到的变量是有帮助的。
@{
int x=123;
string y = "because . ";
}
5. 文本和标记相结合
这个例子显示 了在Razor中混用文本和标记和概念,具体 如下:
@foreach( var item in items){
<span>Item @item.Name.</span>
}
6. 混合代码和纯文本
Razor 查找标签的开始位置以确定何时将代码转移为标记。 然而,有时可能想在一个代码块之后立即输出文本。例如,在下面的例子中就是展示如何在一个条件语句块中显示 纯文本。
@if ( showMessage ) {
<text>This is plain text </text>
}
或
@if ( showMessage ) {@:This is plain text .
}
注意Razor可采用两种不同的方式来混合代码和纯文本。第一种方式是使用 <text> 标签, 这样只是把标签内容写入到了响应中,而标签本身不写入。笔者个人喜欢这种方式,因为它具有逻辑意义。如果想转回标记,只需要使用一个标签就行了。
第二种方式一次只能作用于一行文本。
7. 转义代码分割符。
正如本章前面所阐述的, 可以用 “@@” 来编码 “@” 以达到显示 “@” 的目的。此外,始终都可以选择使用HTML编码来实现。
Razor:
The ASP.NET Twitter Handle is @aspnet
或
The ASP.NET Twitter Handle is @@aspnet
8. 服务器端的注释
@*
This is a multiline server side comment.
@if (showMessage){
<h1>@ViewBag.Message</h1>
}
All of this is commented out.
*@
9. 调用泛型方法
例:
@(Html.SomeMethod<AType>())
布局
Razor的布局有助于使应用程序中的多个视图保持一致的外观。如果熟悉WebForms的话,其中母版页和布局的作用是相同的,但是布局提供了更加简洁的语法和更大的灵活性。
可使用布局为网站定义公共模板(或只是其中的一部分)。公共模板包含一个或多个占位符。应用程序中的其他视图中的其他视图为它们提供内容。从某些角度来看,布局很像视图的抽象基类。
下面来看一个非常简单的布局。这里称这个布局为 SiteLayout.cshtml:
<!DOCTYPE html>
<html>
<head><title>@ViewBag.Title</title></head>
<body>
<h1>@ViewBag.Title</h1>
<div> id="main-content">@RenderBody</div>
</body>
</html>
它看起来像一个标准的Razor视图, 但需要注意的是在视图中有一个@RenderBody调用。这是一个占位符,用来标记使用这个布局的视图将渲染它们的主要内容的位置。多个Razor视图现在可以利用这个布局来显示一致的外观。
接下来看一个使用这个布局的例子 Index.cshtml :
@{
Layout = "~/Views/Shared/SiteLayout.cshtml";
ViewBag.Title = "The Index!";
}
<p>This is the main content!</p>
上面这个视图通过 Layout 属性指定布局。当渲染这个视图时, 它的 HTML 内容将被放在 SiteLayout.cshtml 中id属性值 为 main-content 的DIV元素中, 最后生成的HTML标记如下所示:
<!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>
</body>
</html>
注意视图内容, 其中标题和h1标题都被标记为粗体显示以强调这些都是由视图提供的,除此之外的所有其他内容都由布局提供。
布局可能有多个节。例如,下面示例在前面的布局 SiteLayout.cshtml 的基础上添加一个页脚节:
<!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>
在不做任何改变的情况下再次运行前面的视图, 将会抛出一个异常,提示没有定义Footer节。默认情况下, 视图必须为布局中定义的每一个节提供相应内容。
这是更新后的视图,如下所示:
@{
Layout = "~/Views/Shared/SiteLayout.cshtml";
ViewBag.Title = "The Index!";
}
<p>This is the main content</p>
@section Footer{
This is the <strong>footer</strong>
}
@section语法为布局中定义的一个节指定了内容。
刚才指出:默认情况下, 视图必须为布局中定义的每一个节提供内容。那么当向一个布局中添加一个新节时会如何?这样会引用该布局的每一个视图都不能正常运行吗?
然而,RenderSection方法有一个重载版本,允许指定不需要的节。可以require参数传递一个false值来标记Footer节是可选的:
<footer>@RenderSection("Footer",required:false)</footer>
但是,如果能为视图中没有定义的节定义一些默认的内容,岂不是更好?这里提供了一个方法,虽然有点繁琐,但还是能用:
<footer>
@if (IsSectionDefined("Footer")){
RenderSection("Footer");
}
else{
<span>This is the default footer.</span>
}
</footer>
第15 章会介绍Razor语法的一个高级特性,称之为模板Razor委托,利用它可以实现一个更好的方法来解决这个问题。
ViewStart
在前面的例子中,每一个视图都是使用Layout属性来指定它的布局。如果多个视图使用同一个布局,就会产生冗余,并且很难维护。
_ViewStart.cshtml 页面可用来消除这种冗余。这个文件中的代码先于同目录下任何视图代码的执行。这个文件也可以递归地应用到子目录下的任何视图。
当创建一个默认的ASP.NET MVC项目时,你将会注意到在Views目录下会自动添加一个_ViewStart.csthml 文件,它指定了一个默认布局。
@{
Layout="~/Views/Shared/_Layout.cshtml";
}
因为这个代码先于任何视图运行,所以一个视图可以重写Layout 属性的默认值,从而重新选择一个不同的布局。如果一组视图拥有共同的设置,那么_ViewStart.cshtml文件就有了用武之地。因为我们可以在它里面对共同的视图配置进行统一设置。如果有视图需要覆盖统一的设置,我们只需要修改对应的属性值即可。
3.8 指定部分视图
除了返回部分视图之外,操作方法也可以通过PartialView方法以PartialViewResult的形式返回部分视图。下面是一个例子。
public class HomeController : Controller{
public ActionResult Message(){
ViewBag.Message = "This is a partial view.";
return PartialView();
}
}
这种情形下,渲染的是视图Message.cshtml, 但是如果布局是由_ViewStart.cshtml页面指定(而不是直接在视图中)的,将无未能渲染视图。
除了不能指定布局之外,部分视图看起来和正常视图没有分别:
<h2>@ViewBag.Message</h2>
在使用Ajax技术进行部分更新时, 部分视图是很有用的。 下面展示了一个非常简单的例子,使用jQuery 将一个部分视图的内容加载到一个使用Ajax调用的当前视图中:
<div id="result" ></div>
@section scripts{
<script type="text/javascript" >
$(function(){
$("#result").load("/home/message");
});
</script>
}
前面的代码使用jQuery 的load方法向Message操作方法发出一个Ajax请求, 而后使用请求的结果更新id属性值为result的div元素。
这里加入一点个人测试的结果:
jQuery.load 部分视图后 chrome F12的console提示有警告:
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
不赞成在主线程中使用同步请求,因为这会对最终的用户体验带来不利影响。
但是, 换了下面这种 ajax 的异步请求, 还是有这个提示, 不太明白, 难道是这种写法强制了必须同步才能完成?
<div id="result">
</div>
@section scripts{
<script type="text/javascript">
$(function () {
//$("#result").load("/MvcMusicStore/store/message");
$.ajax({
url: "/MvcMusicStore/store/message",
async: true,
success: function (result) {
$("#result").html(result);
}
});
});
</script>
}
想要看到前面两节中描述的指定视图和部分视图的例子,可以使用NuGet将Wrox, ProMvc5.Views.SpecifyingViews包安装到一个默认的ASP.NET MVC5项目占。像下面这样:
Install-Package Wrox.ProMvc5.Views.SpecifyingViews
这将在项目的示例目录下添加一个包含有多个操作方法的控制器示例,每一个操作方法以不同的方式指定一个视图。 可以按Ctrl+F5访问如下目录:
/sample/index
/sample/index2
/sample/index3
/sample/partialviewdemo
3.9 小结
视图引擎的用途非常有限。它们的目的是获取从控制器传递给它们的数据,并生成经过格式化的输出,通常是HTML格式。除了这些简单的职责或“关注点”之外,作为开发人员,还可以以任意想要的方式来实现视图的目标。Razor视图引擎简单直观的语法使用使得编写丰富安全的页面极其容易,而不必考虑编写页面的难易程度。