ASP.NET MVC5 基础系列(3)——视图
一、视图约定
当创建一个项目模版时,可以注意到,项目以一种非常具体的方式包含了一个结构化的Views目录。在每一个控制器的View文件夹中,每一个操作方法都有一个同名的视图文件与其对应。(约定大于配置)这就提供了视图与操作方法关联的基础。
1 public ActionResult Index() 2 { 3 return View(); 4 }
视图选择逻辑在/Views/ControllerName目录(这里就是去掉Controller后缀的控制器名)下查找与操作方法同名的视图。此处选择的是/Views/Home/Index.cshtml。与ASP.NET MVC中的大部分方法一样,这一约定是可以重写的。想让Index操作方法渲染一个不同的视图,可以向其提供一个不同的视图名称,代码如下:
1 public ActionResult Index() 2 { 3 return View("NotIndex"); 4 }
对于上面的编码,操作方法依然在/Views/Home目录中查找视图,但选择的不再是Index.cshtml,而是NotIndex.cshtml。如果需要制定完全位于不同目录结构中的视图,编码如下:
1 public ActionResult Index() 2 { 3 return View("~/Views/Example/Index.cshtml"); 4 }
二、 强类型视图
假设需要编写一个显示Album实例列表的视图,一种方法是将专辑添加到ViewBag中,然后在视图中进行迭代。
1 public ActionResult List() 2 { 3 var albums = new List<Album>(); 4 for (int i = 0; i < 10; i++) 5 { 6 albums.Add(new Album { Title = "Product" + i }); 7 } 8 ViewBag.Albums = albums; 9 return View(); 10 }
然后,再在视图中迭代显示,如下代码:
1 <ul> 2 @foreach (Album a in (ViewBag.Albums as IEnumerable<Album>)) 3 { 4 <li>@a.Tilte</li> 5 } 6 </ul>
注意在枚举之前需要将动态的ViewBag.Albums转换为IEnumerable<Album>类型。为了使代码整洁,可以使用dynamic关键字,但是当访问每个Album对象的属性时,就不能再使用智能感知功能。
1 <ul> 2 @foreach (dynamic p in ViewBag.Albums) 3 { 4 <li>@p.Tilte</li> 5 } 6 </ul>
强类型视图既能获得dynamic的简洁语法,又能获得强类型和编译时检查的好处(比如正确的输入属性和方法名称)。强类型视图允许设置视图的模型类型。因此可以从控制器向视图传递一个在两端都是强类型的模型对象,从而获得智能感知、编译器检查等好处。在Controller方法中,可以通过向重载的View方法中传递模型实例来指定模型,代码如下:
1 public ActionResult List() 2 { 3 var Musics = new List<MusicModels>(); 4 for (int i = 0; i < 10; i++) 5 { 6 Musics.Add(new MusicModels { MusicName = "MusicName" + i.ToString() }); 7 } 8 return View(Musics); 9 }
下一步是告知视图哪种类型的模型正在使用@model声明。但要注意这里需要输入模型类型的完全限定类型名(名称空间和类型名称),如下所示
1 @model IEnumerable<MvcMusicStore.Models.MusicModels> 2 <ul> 3 @foreach(MvcMusicStore.Models.MusicModels music in Model) 4 <li>@music.SingerName</li> 5 </ul>
如果不想输入模型类型的完全限定类型名,可使用@using关键字,如下所示
1 @using MvcMusicStore.Models 2 @model IEnumerable<MusicModels> 3 <ul> 4 @foreach(MusicModels music in Model) 5 <li>@music.SingerName</li> 6 </ul>
对于在视图中经常使用的名称空间,好的方法是在Views目录下的web.config文件中声明:
<add namespace="MvcMusicStore.Models">
三、 理解ViewBag、ViewData和ViewDataDictionary
之前介绍了使用ViewBag从控制器向视图传递信息,然后介绍了传递强类型模型。现实中,这些都是通过ViewDataDictionary传递的。从技术的角度看,数据从控制器传送到视图是通过一个名为ViewData的ViewDataDictionary(这是一个特殊的字典类)。我们可以使用标准的字典语法设置或读取其中的值:
ViewData["CurrentTime"] = DateTime.Now;
尽管这种语法现在也能用,但是MVC3提供了更简单的语法,可以利用C#4的dynamic关键字。ViewBag是ViewData的动态封装器。这样我们就可以按照下面的方式来设置值:
ViewBag.CurrentTime = DateTime.Now;
ViewBag.CurrentTime和ViewData["CurrentTime"] 起到了等同的作用。一般来说,大部分代码使用ViewBag,而不是ViewData,这两种语法并不存在技术上的差异,仅仅是因为ViewBag相对于字典语法而言看上去好看。
注意,ViewBag和ViewData的差异:
- 只有当要访问的关键字是一个有效的C#标识符时,ViewBag才起作用。例如,如果在ViewData["Key With Spaces"]中存放一个值,那么就不用使用ViewBag访问,因为无法通过编译。
- 动态值不能作为一个参数传递给扩展方法,因为C#编译器为了选择正确的扩展方法,在编译时必须知道每一个参数的真正类型。
四、Razor语法
在演示Razor语法的使用之前,我们需要做一些准备工作。
1.打开VS创建一个ASP.NET MVC空项目,很简单,就不具体演示了。
2.添加一个Model。在项目的Models文件夹中添加一个名为Product的类。代码如下:
namespace MvcApplication1.Models { public class Product { public int ProductID { get; set; } public string Name { get; set; } public string Description { get; set; } public decimal Price { get; set; } public string Category { set; get; } } }
3.添加一个Controller。右击项目中的Controllers文件夹,选择添加控制器,命名如下图所示:
点添加后,对ProdcutController中的代码进行如下编辑:
using System.Web.Mvc; using MvcApplication1.Models; namespace MvcApplication1.Controllers { public class ProductController : Controller { public ActionResult Index() { Product myProduct = new Product { ProductID = 1, Name = "苹果", Description = "又大又红的苹果", Category = "水果", Price = 5.9M }; return View(myProduct); } } }
4.添加一个View。右击Index方法,选择添加视图,在弹出的窗口进行如下配置:
点添加后,系统自动帮我们创建一个Product文件夹和一个Index.cshtml文件,Index.cshtml内容如下:
@model MvcApplication1.Models.Product @{ ViewBag.Title = "Index"; } <h2>Index</h2>
5.修改默认路由。为了方便,我们应该让应用程序启动时直接导向我们需要的请求处理(此处是Product/Index)。打开Global.asax文件,找到注册路由RegisterRoutes方法下的routes.MapRoute方法,把controller的值改为“Product”,如下所示:
routes.MapRoute( "Default", // 路由名称 "{controller}/{action}/{id}", // 带有参数的 URL new { controller = "Product", action = "Index", id = UrlParameter.Optional } // 参数默认值 );
使用Model对象介绍Razor语法,让我们从Index.cshtml文件的第一行开始:
@model MvcApplication1.Models.Product
Razor语句都是以@符号开始的。每个视图都有自己的Model属性(通过@Model调用)。上面这句代码的意思是将本视图的Model属性的类型指向MvcApplication1.Models.Product类型,这就实现了强类型。强类型的好处之一是类型安全,如果写错了Model对象的某个成员名,编译器会报错;另一个好处是在VS中可以使用VS中的代码智能提示自动完成类型成员调用的代码编写。 当然这句代码不要程序也可以正常运行,只是给编写代码造成了一定的困难。
视图中的Model属性用于存放控制器(Controller)传递过来的model实例对象(本示例中ProductController通过“return View(myProduct)”传递给Index视图),下面的代码演示了如何调用该model对象:
@model MvcApplication1.Models.Product @{ ViewBag.Title = "Index"; } <!-- 调用Product实例的Name属性 --> <h2>名称:@Model.Name</h2>
注意,第一行代码用于声名Model属性类型用的是@model <Model类型名>(小写m),而调用控制器传递过来的Model对象用的是@Model.<属性名>(大写M)。按F5运行效果如下:
(一)使用表达式
上面讲的使用Model对象是很常用的一种Razor代码。其实上面示例中的@Model.Name就是一个简单的表达式,表示向Web页面呈现Model.Name的文本值。Razor语法中的表达式除了可以使用Model对象,也可以使用几乎任何一个其他可访问权限范围内的对象,来向Web面面输出该对象成员的文本值。如下代码所示:
@model MvcApplication1.Models.Product
@{
ViewBag.Title = "Index";
}
现在的时间是: @DateTime.Now.ToShortTimeString()
运行效果如下:
这种使用对象的简单表达式(@DateTime.Now.ToShortTimeString()和@Model.Name),在这我们不防称之为对象表达式。除了对象表达式,还可以是其他任意的有返回值的表达式,如条件表达式。如下面代码所示:
@model MvcApplication1.Models.Product @{ ViewBag.Title = "Index"; } 现在的时间是: @DateTime.Now.ToShortTimeString() <br/>@(DateTime.Now.Hour>22 ? "还早,再写一会吧!" : "该睡觉咯!")
运行效果如下:
注意,一般使用非对象表达式时都需要用小括号括起来。
(二)使用代码块
和表达式的使用方式一样,Razor语法中也可以使用由{}括起来的单个C#过程控制代码块(如if、switch、for等)。使用方式如下:
@model MvcApplication1.Models.Product @{ ViewBag.Title = "Index"; } @if (Model.Price > 5M) { string test = "买不起!"; <p>@Model.Name <b>太贵了!</b> @test </p> }
效果如下:
由{}括起来的代码块内可以写任何C#代码,也可以使用任何HTML标签。但需注意的是,当控制语句内只有一句代码时不能像写C#后台代码一样省略大括号。还有一种更常用的使用代码块的方式。你也可以通过以@{开始,以}闭合的方式来使用代码块,它可以把多个代码块放在一起,开成一个更大的代码块。如下代码所示:
@model MvcApplication1.Models.Product @{ ViewBag.Title = "Index"; } @{ if(Model.Category=="水果"){ string test="是一种水果。"; @Model.Name @test } if (Model.Price > 5M) { string test = "买不起!"; <p>@Model.Name <b>太贵了!</b> @test </p> } }
运行结果如下:
(三)使用@:和text标签
我们注意到,在代码块中,要么是C#代码,要么是HTML标签,不能直接写纯文字,纯文字须包裹在HTML标签内。但如果需要在代码块中直接输出纯文字而不带HTML标签,则可以使用@:标签,在代码块中输出纯文本文字非常有用。如下代码所示:
@if (Model.Price > 5M) { @Model.Name@:太贵了 。 <br /> @: @@:后面可以是一行除@字符以外的任意文本,包括<、>和空格,怎么写的就怎么输出。 <br /> @: 如果要输出@符号,当@符号前后都有非敏感字符(如<、{、和空格等)时,可以直接使用@符号,否则需要使用两个@符号。 }
注意@符号的使用。上面代码运行效果如下:
使用@:标签在代码块中输出一行不带html标签的文本非常方便,但如果需要在代码块中输出续或不连续的多行纯文本,则使用text标签较为方便,如下代码所示:
@if (Model.Price > 5M) { <text> 名称:<b>@Model.Name</b><br /> 分类:<b>@Model.Description</b><br /> 价钱:<b>@Model.Price</b><br /> <pre> 测试行一: <a>aaaa</a> 测试行二: @@ fda@aaa </pre> </text> }
运行结果:
五、简单分部视图
创建一个PartialView,在解决方案资源管理器中右键点击Shared文件夹选择添加->MVC 5 分部页(Razor)。如下图所示:
文件命名为PartialPage.cshtml,写入如下代码:
<h2>This is a partial page.</h2>
这样我们就创建好了一个简单的分部视图,现在我们来创建一个Controller和View来调用它。
在Controllers文件夹下创建PartialViewController.cs并写入如下代码:
一个最简单的Controller,就是为了让大家好理解。右键点击上面的"Index"函数名,选择添加视图。系统会在~\Views\PartialView\文件夹下创建Index.cshtml文件,在这个文件中写入如下代码:
上面代码中第1-3行表示在分部视图中不用加载模板。<hr />是下图所示的分隔线。
从下面显示结果可以看出,PatialPage.cshtml中的内容被显示了两次,这对应两个不同的调用分部视图的函数。第6行Html.Partial函数的作用是返回所调用的PartialView中的内容。其所在的View会负责输出其返回的内容。而第9行的代码则是直接输出所调用的PartialView中的内容。
如果还是不清楚Partial和RenderPartial的关系,可以这样类比:比如我们有一个string叫s,Partial和RenderPartial的关系就相当于s.ToString()和Response.Write(s.ToString())的关系一样。前者是返回内容,后者是输出内容。
(一)带Model的分部视图
前面只是创建了一个静态分部视图,下面我们来把它改造一下来显示Model数据。修改~\Views\PartialView\Index.cshtml文件,代码如下:
上面的第6和9行,加入了第二个参数,是一个数字。这个数字就是我们要传给PartialView的Model。修改~\Views\Shared\PartialView.cshtml文件,内容如下:
@model int <h2>This is a partial page @Model.</h2>
第1行表示传入的model是int类型。第2行把这个数字显示出来。显示结果如下:
(二)使用ChildAction调用分部视图
前面调用PartialView的方式都是通过一个View来调用PartialView。下面我们来介绍通过View调用ChildAction来返回PartialView。
首先在PartialViewController.cs里面写一个ChildAction代码如下:
第1行,在ChildAction函数的前面写上[ChildActionOnly]表示这个Action只能作为ChildAction使用。ChildAction返回partialView的好处就是在Action里可以做一些处理和控制。这里第4到16行就是根据获得的时间返回不同的问候语。第17行返回其对应的PartialView并传入greetings作为Model。右键点击ChildAction函数名选择创建视图,取名为ChildAction。写入如下代码:
@model string
<h2>@Model</h2>
这个PartialView很简单,就是把传入的Model显示出来。修改~\Views\PartialView\Index.cshtml文件,代码如下:
(三)ajax无刷新更新分部视图
要通过ajax来调用ChildAction返回PartialView,首先要去掉ChildAction开头写的[ChildActionOnly]。因为这种调用方法不算ChildAction调用。然后修改~\Views\PartialView\Index.cshtml文件,代码如下:
这样就完成了无刷新更新局部页面数据。在视图里有多种方法可以 加载部分视图,包括:Partial() Action() RenderPartial() RenderAction() RenderPage() 方法。以下是这些方法的差别:
(四)Partial 与 RenderPartial 方法
1. Razor 语法:@Html.Partial() 与 @{Html.RenderPartial();}
2. 区别:Partial 可以直接输出内容,它内部是 将 html 内容转换为 string 字符(MVCHtmlString),然后缓存起来, 最后在一次性输出到页面。显然,这个转换的过程,会降低效率,所以通常使用 RenderPartial 代替。
(五)RenderPartial 与 RenderAction 方法
1. Razor 语法:@{Html.RenderPartial();} 与 @{Html.RenderAction();}
2. 区别:RenderPartial 不需要创建 Controller 的 Action ,而 RenderAction 需要在 Controller 创建要加载的 Action。
RenderAction 会先去调用 Contorller 的 Action ,最后再 呈现视图,所以这里 页面会在 发起一个链接。
如果这个部分视图只是一些简单 的 html 代码,请使用 RenderPartial。 但如果这个部分视图 除了有 html 代码外,还需要通过读取数据库里的数据 来渲染,就必须使用 RenderAction 了,因为它可以在 Action 里调用 Model里的方法读取数据库,渲染视图后在呈现,而 RenderPartial 没有 Action,所以无法做到。
(六)RenderAction 与 Action
1. Razor 语法:@{Html.RenderAction();} 与 @Html.Action();
2. 区别:Action 也是直接输出,和 Partial 一样,也存在一个转换的过程。不如 RenderAction 直接输出到当前HttpContext 的效率高。
(七)RenderPage 与 RenderPartial 方法
1. Razor 语法:@{Html.RenderPartial();} 与 @RenderPage()
2. 区别:也可以使用 RenderPage 来呈现部分,但它不能使用 原来视图的 Model 和 ViewData ,只能通过参数来传递。而 RenderPartial 可以使用原来视图的 Model 和 ViewData。