Pro ASP.NET MVC –第五章 使用Razor
Razor是微软在MVC3中引入的视图引擎的名字,在MVC4中对其进行了改进(尽管改动非常小)。视图引擎处理ASP.NET内容、寻找指令,典型地用于插入动态数据并输出到浏览器中。微软维持了两个视图引擎——ASPX视图引擎工作与<%%>标签,ASP.NET已经依赖它多年;RAZOR引擎工作与@字符后的内容块上。
总的来说,如果你熟悉<%%>语法,那么你就不会在使用Razor时有太多问题,尽管Razor中有一些新的规则。在本章,我们将为你介绍Razor语法,以使你可以在看到它们的时候能认出这些新元素。在本章,我们并不会提供大量的Razor参考,因为这么做会破坏课程结构。但我们在本书后续章节中深入介绍Razor
1创建示例项目
为了演示Razor的特性和语法,我们需要创建一个新的MVC4工程。
定义模型
publicclassProduct { publicint ProductID { get; set; } publicstring Name { get; set; } publicstring Description { get; set; } publicdecimal Price { get; set; } publicstring Category { get; set; } } |
定义控制器
publicclassHomeController : Controller { Product product = newProduct { ProductID = 1, Name = "Kayak", Description = "A boat for one person", Category = "Watersports", Price = 275M };
publicActionResult Index() { return View(product); }
} |
创建视图
@model MvcRazor.Models.Product
@{ Layout = null; }
<!DOCTYPEhtml>
<html> <head> <metaname="viewport"content="width=device-width"/> <title>Index</title> </head> <body> <div>
</div> </body> </html> |
2使用模型对象
我们在视图中添加如下的一行(粗体代码)
@model MvcRazor.Models.Product
@{ Layout = null; }
<!DOCTYPEhtml>
<html> <head> <metaname="viewport"content="width=device-width"/> <title>Index</title> </head> <body> <div> @Model.Name </div> </body> </html> |
Razor语句以@字符开始。在我们的例子中,@model语句声明将传递给视图使用的来自于行为方法的模型对象。然后通过@Model,我们就可以调用模型对象的方法,字段和属性。此时你运行工程,那么将会得到如下的结果:
通过使用@model表达式,我们告诉MVC使用什么类型的对象,同时Visual Studio也可从中获益。首先,当你在编写视图中,一旦你在Visual Studio中输入@model后,Visual Studio将自动列出该对象的属性、字段以及方法
此外,如果你输入一个该对象不存在的成员,那么Visual Studio将提示错误
3 使用布局
当前,在Index.cshtml中,有如下一句话
…… @{ Layout = null; } …… |
这是一个Razor代码块,它允许我们在视图中使用C#语句。代码块以@{开始,以}结束。当呈现视图的时候,视图中的代码块被执行。在我们的例子中,代码设置Layout属性的值为null。在MVC程序中Razor视图被编译成C#类,而其基类(RazorView)定义了一个Layout属性,我们在18章中我们将介绍更详细的内容。在这里,我们只需要知道当该属性设为null表明,当前视图是自我包含的,并且将呈现我们所需的所有内容到客户端。
自我包含的视图对于简单的应用已经足够,但是一个真正的项目会包含大量的视图。布局是一种有效的模板,这些模板包含的标记内容可以使你的多个网页保持一致性——这就可以确保正确JavaScript库被使用,或者创建通用的模块供你的项目使用。
创建布局
为了创建布局,你可以在视图文件夹上点击右键,然后选择添加,然后选择MVC4布局页面(Razor)模板
在出现的对话框中,把布局文件命名为_BasicLayout.cshtml
然后点击确认按钮。下面的代码列出布局文件的基本内容
<!DOCTYPEhtml>
<html> <head> <metaname="viewport"content="width=device-width"/> <title>@ViewBag.Title</title> </head> <body> <div> @RenderBody() </div> </body> </html> |
布局是一种特定类型的视图,你可以发现它也包含@表达式。调用@RenderBody方法将把由行为方法指定的视图的内容插入到布局文件中的对应的标记中。另外一个Razor表达式用于查找Viewbag中的Title属性,然后把其值设置到页面的title元素中。
布局文件中的所有元素都将应用到使用该布局文件的视图中,这也就是为什么说视图就是模板。在下面的代码中,我们添加了一些标记以演示它们是如何工作的
<!DOCTYPEhtml>
<html> <head> <metaname="viewport"content="width=device-width"/> <title>@ViewBag.Title</title> </head> <body> <div> <h1>Product Information</h1> <divstyle="border:1pxsolidred; line-height:30px;"> @RenderBody() </div> <h2>Visit <ahref="http://apress.com">Apress</a></h2> </div> </body> </html> |
我们向布局文件中添加了两个标题元素,和一个DIV,然后把@renderbody方法放在DIV中。这样我们可以区分哪些内容来自布局文件,哪些内容来自视图。
应用布局
为了应用视图,我们需要设置Index.cshtml的layout属性,此外我们还应该删除HTML标记语言,因为现在由布局文件提供。你可以参考下面的代码应用布局文件到视图
@model MvcRazor.Models.Product
@{ ViewBag.Title = "Product Name"; Layout = "~/Views/_BasicLayout.cshtml"; }
@Model.Name |
你可以发现,即使是一个简单的视图,改动也是巨大的。我们现在只留下我们最关心的并且要呈现给用户的数据。所有的html标记都已经删除。使用布局文件有许多好处,它允许我们简化数图;允许我们创建通用的HTML供多个视图使用;它还使维护变得简单因为我们可以值在一个共用的地方更改HTML,更改后的结果就会应用到所有使用该布局文件的视图。下图展示了使用布局文件的效果
使用视图开始文件
还有一个小疑惑我们需要指出,那就是我们需要在每个视图文件中指出我们需要使用的布局文件。这就意味着如果我们需要重命名布局文件,那么我们就需要查找每个使用了该布局文件的视图然后做出相应的更改,在这个过程中很容易发生错误,这也违背了MVC框架的易维护性。
我们可以通过使用视图开始文件来解决这个问题。当呈现一个视图时,MVC框架将查找名为_ViewStart.cshtml的文件。该文件的内容被当作它好像包含在视图文件自身中,我们可以使用这个特性来自动地设置layout属性的值。
为了创建一个视图开始文件,添加一个新视图到Views文件夹下,然后将其命名为_ViewStart.cshtml,然后设置其内容
@{ Layout = "~/Views/_BasicLayout.cshtml"; } |
然后我们更改Index.cshtml的内容
@model MvcRazor.Models.Product
@{ ViewBag.Title = "Product Name"; }
@Model.Name |
我们不必指定我们想使用的视图开始文件。MVC框架将自动找到该文件并自动地使用视图开始文件的内容。请注意,视图文件中的layout的优先级更高。因此,当在视图文件中指定了Layout之后,会自动覆盖视图开始文件中的layout属性。
演示共享布局
为了演示共享布局,我们添加一个新的行为方法NameAndPrice到Home控制器中。
publicActionResult NameAndPrice() { return View(product); } |
然后,在NameAndPrice上点击右键,选择创建视图,执行视图所使用的模型类为Product,并选择_BasicLayout.cshtml为该视图的布局文件。生产视图后,添加下面加粗的代码
@model MvcRazor.Models.Product
@{ ViewBag.Title = "NameAndPrice"; Layout = "~/Views/_BasicLayout.cshtml"; }
<h2>NameAndPrice</h2> The product name is @Model.Name and its costs $@Model.Price |
启动应用层序,然后得到如下的结果
4使用Razor表达式
现在,我们已经展示了视图和布局的基本使用方法,接下来我们将把注意力集中到Razor所支持的各种表达式上并了解如何使用这些表达式。
在一个优秀的MVC程序中,在不同的行为方法和视图的执行之间有清楚的界限。在本章,规则很简单,我们把它总结成下表所示的内容
组件 |
应该赋予的角色 |
不应该赋予的角色 |
行为方法 |
传递视图模型对象到视图 |
传递格式化的数据到视图 |
视图 |
使用视图模型对象,把其内容呈现给用户 |
更改视图模型对象的任何方法 |
在本书后续的章节中,我们将不断的回顾上表所述的规则。为了最大化地利用MVC框架,你应该在程序中的各个部分重视并强制实现隔离。因为你将看到,你可以使用Razor做很多事情,包括在Razor中使用C#语句,但是你绝对不应该使用Razor去执行业务逻辑,或者使用任何方式更改域模型对象。
同样地,你不应该在行为方法中格式化数据,然后将其传递给视图。相反地,应该让视图按照所需的方式呈现数据。回顾本章之前的小节,你会发现我们定义的行为方法NameAndPrice,它用于显示Product对象的Name属性和Price属性。即使我们知道将在页面上显示哪些属性。我们也应传递一个完整的Product对象到视图模型。正如这样:
publicActionResult NameAndPrice() { return View(product); } |
当我们在视图中使用Razor @Model表达式去获取属性的时候,采用了下面的方式
The product name is @Model.Name and its costs $@Model.Price |
我们可以通过在视图方法中创建一个字符串显示我们需要的结果,并将其作为视图模型对象传递给视图。它也可以实现同样的结果,但是这种实现方式破坏了MVC模式,并且减少了随需变化的能力。正如我们说过的,我们需要回顾行为方法和视图之前的准则。你应当记住,虽然MVC框架并没有要求正确使用MVC模式,但是我们还是应该在设计和编码时遵循MVC模式。
插入数据值
使用Razor表达能做的最简单的事情就是向标记语言中插入数据。你可以使用@Model表达式引用视图模型对象的属性和方法,或使用@ViewBag表达式引用所定义的动态属性。
你已经见过上面两种情形的例子。但为了完整性,我们在Home控制器中,添加一个名为DemoExpressions的行为方法,它向视图传递模型对象和viewbag。
publicActionResult DemoExpressions() { ViewBag.ProductCount = 1; ViewBag.ExpressShip = true; ViewBag.ApplyDiscount = false; ViewBag.Supplier = null;
return View(product); } |
然后,我们创建强类型的视图DemoExpression.cshtml
@model MvcRazor.Models.Product
@{ ViewBag.Title = "DemoExpressions"; }
<table> <thead> <tr> <th>Property</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>Name</td> <td>@Model.Name</td> </tr> <tr> <td>Price</td> <td>@Model.Price</td> </tr> <tr> <td>Stock Level</td> <td>@ViewBag.ProductCount</td> </tr> </tbody> </table> |
我们创建了一个简单的HTML table,并获取模型对象和viewbag的属性的值。该视图的结果如下:
页面看起来不太好,因为我们没有对HTML元素应用CSS样式。但这个例子强调了如何使用Razor表达式来显示从行为方法传递到视图的数据,
设置特性值
到目前为止的四个例子都是想元素设置内容,此外你还可以使用Razor表达式设置原色的特性。下面我们基于DemoExpress视图,添加代码实现对元素特性的设置
@model MvcRazor.Models.Product
@{ ViewBag.Title = "DemoExpressions"; }
<table> <thead> <tr> <th>Property</th> <th>Value</th> </tr> </thead> <tbody> <tr> <td>Name</td> <td>@Model.Name</td> </tr> <tr> <td>Price</td> <td>@Model.Price</td> </tr> <tr> <td>Stock Level</td> <td>@ViewBag.ProductCount</td> </tr> </tbody> </table>
<divdata-discount="@ViewBag.ApplyDiscount"data-express="@ViewBag.ExpressShip" data-supplier="@ViewBag.Supplier"> The containing element has data attributes </div>
Discount:<inputtype="checkbox"checked="@ViewBag.ApplyDiscount"/> Express:<inputtype="checkbox"checked="@ViewBag.ExpressShip"/> Supplier:<inputtype="checkbox"checked="@ViewBag.Supplier"/> |
我们使用基本的Razor表达式为div设置data-*特性的值。Data特性,它们是以data-为前缀的特性,已经成为非正式的创建自定义特性的方式很多年了,现在已经逐渐成为HTML5的正式的标准。我们通过ViewBag的属性ApplyDiscount,ExpressShip和Supplier的值为DIV设置了对应的特性。
如果你运行程序,那么在浏览器中,你会看到DIV的特性的值已经正确地呈现出
False和True对应Viewbag的布尔值,请注意Razor已经对值为NULL的属性做了特别的处理,因此data-supplier的值为空字符串。
当我们再次观察生成的页面,你会发现一件有趣的事情,那就是checkbox的checked特性
在MVC4中,Razor可以采用一种更有意识的方式使用像checked这样的特性,其使用方式就是是否呈现该特性,而不是呈现该特性的值。如果向Razor插入了一个False、null或空白字符串作为chekced特性的值,那么显示在浏览器中的checked的特性将被删除。否则,将显示为已选中的状态
使用条件语句
Razor还可以处理条件语句,这就意味着我们可以从数图中基于视图数据的值调整输出结果。我们开始接触Razor的核心,它可以允许你创建爱你复杂并流畅的布局,同时它十分简单,不仅容易阅读还便于维护。下面的视图展示了一个使用条件语句的视图:
<tr> <td>Stock Level</td> <td> @switch ((int)ViewBag.ProductCount) { case 0: @: out of Stock break; case 1: <b>Low stock (@ViewBag.ProductCount)</b> break; default: @ViewBag.ProductCount break; } </td> </tr> |
若要使用条件语句,你应该放置@符号在C#条件关键字前,在我们的例子中,就是在switch前放置@符号。如同C#一样,你使用}结束代码片段。
在Razor代码片段中,你可以通过HTML和Razor表达式使用HTML元素和视图中的数据值。比如上面例子中的
@:<b>Low stock (@ViewBag.ProductCount)</b> |
我们并没有把这些表达式放在引号或其他特殊的符号中,因为Razor引擎可以识别这些表达式,使其看起来仿佛按照常规方式处理的结果。但是,如果你先插入文本到视图中,并且这个文本没有包含在一个HTML元素中,那么你需要使用下面这种方式:
@: Out of stock |
@:使Razor将其后的内容当作C#语句,这也是Razor遇到文本输出时的默认行为。运行应用程序,你可以在浏览器中看到如下的结果
条件表达式在Razor视图中非常重要,因为它允许你根据行为方法产生的数据值调整视图的内容。下面是另外一个例子,演示了如何使用if语句
<td> @if (ViewBag.ProductCount == 0) { @: Out of stock } elseif (ViewBag.ProductCount == 1) { <b>Low stock (@ViewBag.ProductCount)</b> }else{ @ViewBag.ProductCount } </td> |
上面的条件与switch语句产生相同的结果,但我们希望向你演示如何使用C#条件语句。在第18章中,我们将做更详细的介绍。
枚举数据和集合
当编写一个MVC程序时,你可能经常希望枚举一个数组或一些其他类型的集合,然后根据每个子项生成内容。为了演示如何实现这个目的,我们在Home控制器中定义一个新的行为方法DemoArray
publicActionResult DemoArray() { Product[] products = { newProduct {Name = "Kayak", Price = 275M}, newProduct {Name = "Lifejacket", Price = 48.95M}, newProduct {Name = "Soccer ball", Price = 19.50M}, newProduct {Name = "Corner flag", Price = 34.95M} };
return View(products); } |
该行为方法创建一个Product[]对象,它包含一些简单的数据值并传递给View方法,以使数据可以通过默认的视图呈现。
在创建视图时,Visual studio并没有提供数组和集合的支持,因此你需要手动设置模型类的类型
然后在生成的视图中,你可以看到model的类型为:@model MvcRazor.Models.Product[]。下面是生产视图的代码
@model MvcRazor.Models.Product[]
@{ ViewBag.Title = "DemoArray"; Layout = "~/Views/_BasicLayout.cshtml"; }
@if (Model.Length > 0) { <table> <thead><tr><th>Product</th><th>Price</th></tr></thead> <tbody> @foreach (MvcRazor.Models.Product product in Model) { <tr> <td>@product.Name</td> <td>@product.Price</td> </tr> } </tbody> </table> } |
我们使用@if语句去判断返回的数组长度,然后根据长度生成对应的内容。然后使用@foreach表达式枚举数组的内容并在HTML的table中每条数据生成一个html的行。你可以从上面的代码中看到,这些表达式是如何与C#对应的,还可以看到我们在foreach循环中创建了一个本地变量p,然后通过该变量@p.Name和@p.Price引用了该变量的属性。
运行结果为:
处理命名空间
你可能已经注意到,我们在foreach循环中,引用了Product的完整名(包含了命名空间)【注:其实我们在MVC4中,也可以直接使用var】。
@foreach (MvcRazor.Models.Product product in Model) { |
这在复杂的视图中,会令人懊恼,因为很可能你会在多个地方都需要引用视图模型和其他类(那么每个都需要包含命名空间)。我们可以通过应用@using表达式来简化我们的代码
@using MvcRazor.Models @model Product[]
@{ ViewBag.Title = "DemoArray"; Layout = "~/Views/_BasicLayout.cshtml"; }
@if (Model.Length > 0) { <table> <thead><tr><th>Product</th><th>Price</th></tr></thead> <tbody> @foreach (Product product in Model) { <tr> <td>@product.Name</td> <td>@product.Price</td> </tr> } </tbody> </table> } |
视图可以包含多个@using表达好似。上面的例子中我们使用@using表达式引入了MvcRazor.Modes命名空间,这就意味着我们可以在foreach循环中取出命名空间。
总结
在本章,我们概览了Razor视图引擎,已经如何使用它来生成HTML。我们还为你展示了如何通过视图模型对象和Viewbag对象引用控制器传递过来的数据,此外我们还介绍了如何使用Razor表达式呈现数据。在本书的后续章节你还会看到Razor的一些其他的例子;此外在第十八章,我们会详细介绍MVC视图的工作机制。在下一章,我们将描述开发和测试MVC的一些基本的工具;从而使你可以在你的项目中更好的使用这些工具。
【请尊重劳动成果、转载请注明来源】