ASP.NET MVC 5 05 - 视图

时间:2023-03-08 17:43:09

PS:

  唉,这篇随笔国庆(2015年)放假前几天开始的,放完假回来正好又赶上年底,公司各种破事儿。

  这尼玛都写跨年了都,真服了。(=_=#)

  好几次不想写了都。

  可是又不想浪费这么多,狠不下心删除。没办法,慢慢补齐发出来吧。唉。

  大家喜欢的别忘了支持一下,谢谢我猿友们。呵呵。

视图

本篇目录:

1. 前言

2. 视图的作用

3. 视图的基础知识

4. 理解视图约定

5. 强类型视图

5.1 ViewBag的不足

5.2 理解ViewBag、ViewData和ViewDataDictionary1

6. 视图模型

7. 添加视图

8. Razor视图引擎

8.1 Razor的概念

8.2 代码表达式

8.3 HTML编码(防XSS)

8.4 代码块

8.5 Razor语法示例

8.6 布局

8.7 ViewStart

9. 指定部分视图

10. 小结

本篇内容是介绍ASP.NET MVC5中视图及部分视图,以及Razor引擎在视图中的使用技巧等。

慢慢看完吧,为了照顾新手,所以基础的东西可能比较多,后面也有常用技巧等。

还有,记得支持哦。(*^_^*)

1. 前言

  开发人员之所以花费大量的时间来重点设计控制器和模型对象,不是闲着蛋疼,更不是这样会显得多么高大上,而是因为这些精心编写的整洁的代码 是开发一个可维护Web应用程序的基础。

  但是并不是每个用户都是程序员,当他们在浏览器中访问Web应用程序时,这些东西他们是看不到的。

  用户对应用程序的第一印象,一级与应用程序的整个交互的过程都是从视图开始的。

  显而易见,如果应用程序的其他部分存在错误,那么设计再好,在没有瑕疵的视图也不能弥补这方面的不足。

  同样,如果创建一个丑陋而且没什么卵用的视图,那么许多用户将不会给你的系统证明它的功能多么强大、运行多么流畅、性能有多么的高,用户往往直接Ctrl+W关闭了。

  本篇不会介绍如何设计精彩的视图,尽管整洁干净的标记可以使设计工作轻松,但是可视化设计是从呈现内容分离的一个关注点。

  因此,本篇将主要阐述视图在ASP.NET MVC中工作的原理以及职责所在,并提供了工具来创建Web应用程序中引以为豪的视图。

2. 视图的作用

  上一篇内容(控制器)中,我们分析了控制器如何返回输出到浏览器的字符串,这对于控制器的入门非常有帮助。

  但在一些重大的Web应用程序中,我们会注意到一个迅速发展的模式:大部分的控制器操作需要以HTML格式动态显示信息。

  如果控制器操作仅仅返回字符串,那么就需要有大量的字符串替换操作,这样就会变得混乱不堪。

  因此,模板系统的需求越来越清晰,此时,视图应运而生。

  视图的职责是向用户提供一个界面,当提供模型对象(控制器需要显示的信息)后,视图会把模型转换为准备反馈给用户的具体格式,例如对象、字符串、XML、HTML、Json等。。

  在ASP.NET MVC中,完成这一过程有两部分操作,一个事监察有控制器提交的模型对象,另一个则是将内容转换为HTML格式。

注意

  并非所有视图最终都会去渲染成HTML格式。当然,在创建Web应用程序的过程中,HTML是最常用的格式。

  但是,视图也可以渲染成其他类型的内容,例如XML、Json、图像等等,后面我们会一一介绍。

3. 视图的基础知识

  考虑到没玩过ASP.NET MVC的朋友,所以咱们慢点搞,一步步来。

  理解视图原理最简单的方法是查看在一个新ASP.NET MVC应用程序中默认创建的视图。

  首先看一个最简单的例子:不需要控制器提供任何信息的视图。

  打开上一篇中我们创建的项目下的“/Views/Home/Index.cshtml”文件,代码如下所示:

 @{
     ViewBag.Title = "Home Page";
 }

 <div class="jumbotron">
     <h1>ASP.NET</h1>
     <p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
     <p><a href="http://asp.net" class="btn btn-primary btn-lg">Learn more &raquo;</a></p>
 </div>

 <div class="row">
     <div class="col-md-4">
         <h2>Getting started</h2>
         <p>
             ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that
             enables a clean separation of concerns and gives you full control over markup
             for enjoyable, agile development.
         </p>
         <p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301865">Learn more &raquo;</a></p>
     </div>
     <div class="col-md-4">
         <h2>Get more libraries</h2>
         <p>NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.</p>
         <p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301866">Learn more &raquo;</a></p>
     </div>
     <div class="col-md-4">
         <h2>Web Hosting</h2>
         <p>You can easily find a web hosting company that offers the right mix of features and price for your applications.</p>
         <p><a class="btn btn-default" href="http://go.microsoft.com/fwlink/?LinkId=301867">Learn more &raquo;</a></p>
     </div>
 </div>

~/Views/Home/Index.cshtml

  除了顶部设置标题的少量代码,这明显就是标准的HTML文件,下面则是用于触发渲染这个视图的控制器(~/Controllers/HomeController.cs/Index()):

 public ActionResult Index()
 {
     return View();
 }

  浏览到网站的根目录,如图1所示,结果毫不奇怪:HomeController的Index方法渲染了/Home/Index视图。

  也就是将上述视图的HTML内容显示到了header和footer之间。

ASP.NET MVC 5 05 - 视图

图1 - 默认的~/Home/Index视图展示效果

  这个例子非常基础——在最简单的情况中,向控制器发出一个请求,控制器返回一个视图,其实就是一些静态的HTML。

  很容易,但是动态性不好。

  前面说过,视图提供了一个模板引擎,下面我们就利用这个模板引擎,从控制器向视图传递少量参数,最简单的方法就是使用ViewBag。

  ViewBag具有局限性,但是如果只是向视图传递少量数据,他还是很有用的。

  例如下列代码(~/Controllers/HomeController.cs中的About()方法):

 public ActionResult About()
 {
     ViewBag.Message = "Boom、ShaKaLaKa!";

     return View();
 }

  没错,这个About()方法和之前我们看到的Index()方法几乎相同,但是注意第3行,我们在控制器中将 ViewBag.Message 赋值了一个字符串,然后再调用 return View(); 。

  我们再看看对应的视图,也就是~/Views/Home/About.cshtml,代码如下:

 @{
     ViewBag.Title = "About";
 }
 <h2>@ViewBag.Title.</h2>
 <h3>@ViewBag.Message</h3>

 <p>Use this area to provide additional information.</p>

  这个视图很简单,它将页面标题设置为 ViewBag.Title ,然后在标题标签中显示 ViewBag.Title 和 ViewBag.Message 。

  两个ViewBag值前面的@字符是本篇后面将会学习的Razor语法中最重要的部分:它告诉Razor视图引擎,接下来的字符是代码,而不是HTML文本。

  产生的About视图结果如图2所示:

ASP.NET MVC 5 05 - 视图

图2 - ~/Home/About视图默认结果

4. 理解视图约定

  刚才,我们通过两个例子演示了如何使用视图来渲染HTML。下面我们将介绍ASP.NET MVC是怎么找到正确的视图进行渲染,以及如何重写这个视图,为一个控制器的操作方法指定特定的视图。

  本篇到此为止介绍的控制器里面的操作方法都是简单地使用return View();来渲染视图,还不需要指定视图的文件名。

  可以这么做,是因为他们利用了ASP.NET MVC框架的一些隐式约定,这些约定定义了控制器去选择对应视图的逻辑。

  当创建新的项目模板时,我们会注意到,项目以一种非常具体的方式包含了一个结构化的Views目录,如图3所示:

ASP.NET MVC 5 05 - 视图

图3 - 新建项目默认结构

  在这个结构中,每一个控制器都会对应Views中的同名文件夹(不包含Controller的同名文件夹),控制器中的每个方法都对应这个文件夹中的一个同名视图文件(***.cshtml)。

  这就提供了视图与操作方法关联的基础。

  视图选择逻辑在~/Views/ControllerName目录下查找与操作方法同名的视图文件。

  与ASP.NET MVC中的大部分约定设置一样,这一约定是可以重写的。

  如果想让Index操作方法渲染一个不同的视图,可以给它提供一个不同的视图名称,代码如下所示:

 public ActionResult Index()
 {
     return View("NewIndex");
 }

  这样编码后,虽然操作方法仍然在~/Views/Home目录中查找视图,但是选择的就不再是Index.cshtml了,而是NewIndex.cshtml。

  然而,在其他一些应用中,我们可能需要制定完全位于不同目录结构中的视图,针对这种情况,我们可以使用带有~符号的语法来提供视图的完整路径,代码如下所示:

 public ActionResult Index()
 {
     return View("~/Views/Home/NewIndex.cshtml");
 }

  注意,为了查找视图时避开视图引擎的内部查找机制,在使用这种语法时,必须提供视图的文件扩展名(.cshtml)!

5. 强类型视图

  到此为止,本篇的例子都十分简单:通过ViewBag向视图传递少量的参数。

  尽管针对于类似这种简单的情况,使用ViewBag能很轻松的搞定,但是处理实际数据时,ViewBag就不方便了。

  这时就需要使用强类型视图。废话不多说了,来瞅瞅吧。

5.1 ViewBag的不足

  例如,一个控制器的操作方法代码如下:

 public ActionResult List()
 {
     var categories = new List<Category>()
     {
         new Category(){ Title="编程语言" },
         new Category(){ Title="图像设计" },
         new Category(){ Title="服务器运维" },
         new Category(){ Title="少儿动漫" },
         new Category(){ Title="日常生活" }
     };
     ViewBag.Categories = categories;
     return View();
 }

  上述代码中,作为测试,我们定义了一个Category类,其中包含一个公开的字符串类型Title,作为图书类别的标题。

  随后,我们定义了一个List集合,存入了5个Category对象,然后将这个集合对象存入ViewBag.Categories中。

  下面,我们在前台视图遍历输出:

 <ul>
     @{
         foreach (var item in ViewBag.Categories as IEnumerable<BookStore.Models.Category>)
         {
             <li>@item.Title</li>
         }
     }
 </ul>

    注意在枚举之前需要将动态的ViewBag.Categories转换为IEnumberable<Category>类型。

    为了使视图代码干净整洁,在这里也可以使用dynamic关键字,但是当访问每个Category的属性Title时,就不能使用智能感知功能了(不会有代码提示了)。

    使用dynamic的代码如下:

 <ul>
     @{
         foreach (dynamic item in ViewBag.Categories)
         {
             <li>@item.Title</li>
         }
     }
 </ul>

    上述代码运行效果如图4所示:

  ASP.NET MVC 5 05 - 视图

  图4

  可能这时候你会想到:如果,既能利用dynamic简洁的语法,又能使用强类型和编译时检查的好处(输入代码时编译器会根据类型检查给出提示),那就完美了。

  可喜可贺,强类型视图可以帮助我们达到这种需求。

  强类型视图允许设置视图的模型类型,因此,我们可以从控制器向视图传递一个在两端都是强类型的模型对象,从而获得智能感知、编译器检查等功能。

  在控制器的操作方法中,可以通过向重载的View方法中传入模型的实例对象来指定模型的类型,代码如下所示:

 public ActionResult List()
 {
     var categories = new List<Category>()
     {
         new Category(){ Title="编程语言" },
         new Category(){ Title="图像设计" },
         new Category(){ Title="服务器运维" },
         new Category(){ Title="少儿动漫" },
         new Category(){ Title="日常生活" }
     };
     return View(categories);  // 将模型(Category的集合)传入View,用于指定视图的模型类型
 }

  下一步是告诉视图那种类型的模型正在使用@model声明。

  注意这里需要输入模型类型的完全限定类型名(命名空间和类型名称全写),视图代码如下所示:

 @model IEnumerable<BookStore.Models.Category>
 <ul>
     @{
         foreach (BookStore.Models.Category item in Model)
         {
             <li>@item.Title</li>
         }
     }
 </ul>

  如果不想输入类型的完全限定名,可以使用@using关键字声明,代码如下所示:

 @using BookStore.Models
 @model IEnumerable<BookStore.Models.Category>
 <ul>
     @{
         foreach (Category item in Model)
         {
             <li>@item.Title</li>
         }
     }
 </ul>

  对于在视图中经常使用的命名空间,一个比较好的方法就是在Views目录下的Web.config文件中统一声明,如下所示:

  <system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <pages pageBaseType="System.Web.Mvc.WebViewPage">
      <namespaces>
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Optimization"/>
        <add namespace="System.Web.Routing" />

        <!-- 添加常用命名空间 -->
        <add namespace="BookStore" />
        <add namespace="BookStore.Models"/>
      </namespaces>
    </pages>
  </system.web.webPages.razor>

5.2 理解ViewBag、ViewData和ViewDataDictionary

  咱们刚刚已经先讨论了使用ViewBag从控制器向视图传递信息,然后介绍了传递强类型模型。

  现实中,这些值都是通过ViewDataDictionary传递的。下面开始详细介绍。

  从技术上角度讲,数据从控制器传送到视图是通过一个名为ViewData的ViewDataDictionary(这是一个特殊的字典类),我们可以使用标准的字典语法设置或读取其中的值,例如:

 ViewData["CurrentTime"] = DateTime.Now;

  尽管这种语法现在也能使用,但是ASP.NET MVC 3拥有更简单的语法,它利用了C#4的dynamic关键字,ViewBag是ViewData的动态封装器。

  这样,我们就可以按照下面的方式来设置值:

 ViewBag.CurrentTime = DateTime.Now;

  因此, ViewBag.CurrentTime 等同于 ViewData["CurrentTime"] 。

  一般来说,我们大部分代码都是使用ViewBag,而不是ViewData。大多数情况下,这两种语法彼此之间并不存在真正的技术差异(区别)。

  ViewBag相对于字典语法而言仅仅是一种受开发人员欢迎的、看上去很好看的语法而已,毕竟程序员一般都是能偷懒绝干不费事儿的活儿,呵呵。

ViewData和ViewBag

  虽然,深究到底该如何选择哪种语法格式并没有什么技术含量,就像闲的蛋疼,但是之间的差异多多少少我们还是需要了解的。

  很明显的一个差异就是只有当要访问的关键字是一个有效的C#标识符时,才能使用ViewBag。

  例如, ViewData["Zhang DeLong"] ,那么这时如果使用ViewBag。。。怎么写?其中可是有空格的,那么这种写法根本无法通过编译: ViewBag.Zhang DeLong 。

  另一个需要知道的重要差异是,动态值不能作为一个参数传递给扩展方法,因为C#编译器为了选择正确的扩展方法,在编译时必须知道每一个参数的真正类型。如果其中一个参数是动态的,那么久不会通过编译、

  例如,这行代码就会编译失败: @Html.TextBox("Username",ViewBag.Username) 。

  要使这行代码通过编译有两种方法:

  1. 改为使用ViewData: @Html.TextBox("Username", ViewData["Username"]) ;
  2. 使用类型转换,例如: @Html.TextBox("Username", (string)ViewBag.Username) 。

  如刚才我们说的,ViewDataDictionary是一个特殊的字典类,而且不只是一个通用的Dictionary。原因之一在于,它有一个额外的Model属性,允许向视图提供一个具体的模型对象。

  因为ViewData中只能有一个模型对象,所以使用ViewDataDictionary向视图传递具体的类型十分方便。

  这样一来,视图就可以制定它希望哪个类型作为模型对象,从而让我们能够更好的使用强类型。

6. 视图模型

  视图一般需要显示各种没有直接映射到域模型的数据。

  例如,可能需要视图来显示单个商品的详细信息。有时在同一视图上需要显示商品附带的其他信息,比如当前登录系统的用户名、该用户是否有权限编辑商品等等。

  把与视图主模型无关的数据存放在ViewBag属性中,可以很容易地实现这些数据在视图中的显示。

  当具有一个清晰定义的模型和一些额外的引用数据时,这种方法尤为有用。

  这种技术的一种常见的应用是使用ViewBag为下拉列表提供表单选项。

  例如,在咱们之前创建的BookStore项目中,在~/Controllers中新建一个图书的控制器BookController,其中包括一个编辑的操作方法Edit(int id)。

  但是,在编辑目标图书(~/Models/Book.cs)的时候,需要用图书分类(~/Models/Category.cs)及出版社(~/Models/Publishing.cs)作为数据源绑定到下拉框,但是这些数据并不在我们的图书的模型中。

  为了应对这种情况,同时不使用无关信息影响图书模型对象,我们可以将分类及出版社的信息保存到ViewBag中,代码如下:

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;

 namespace BookStore.Models
 {
     /// <summary>
     /// 图书分类
     /// </summary>
     public class Category
     {
         public string Title { get; set; }   // 图书标题
     }
 }

图书分类 ~/Models/Category.cs

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;

 namespace BookStore.Models
 {
     /// <summary>
     /// 出版社
     /// </summary>
     public class Publishing
     {
         public string Title { get; set; }   // 出版社标题
     }
 }

出版社 ~/Models/Publishing.cs

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;

 namespace BookStore.Models
 {
     /// <summary>
     /// 图书信息
     /// </summary>
     public class Book
     {
         public string Title { get; set; }   // 图书标题
     }
 }

图书信息 ~/Models/Book.cs

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;
 using System.Web.Mvc;
 using BookStore.Models;

 namespace BookStore.Controllers
 {
     /// <summary>
     /// 图书控制器
     /// </summary>
     public class BookController : Controller
     {
         /// <summary>
         /// 编辑
         /// </summary>
         /// <param name="id">编辑的目标图书编号,由于现阶段不涉及数据库方面的查询操作,因此这个编号暂时没卵用</param>
         /// <returns>返回Book类的强类型视图</returns>
         )
         {
             // 出版社集合
             IList<Publishing> publishings = new[]{
                 new Publishing(){ Title="清华大学出版社"},
                 new Publishing(){ Title="北京大学出版社"},
                 new Publishing(){ Title="家里蹲大学出版社"}
             };

             // 图书分类集合
             IList<Category> categories = new[] {
                 new Category(){ Title="编程语言" },
                 new Category(){ Title="图像设计" },
                 new Category(){ Title="服务器运维" },
                 new Category(){ Title="少儿动漫" },
                 new Category(){ Title="日常生活" }
             };

             // 图书信息集合
             IList<Book> books = new[] {
                 new Book(){ Title="锋利的jQuery" },
                 new Book(){ Title="ASP.NET MVC5 高级编程" },
                 new Book(){ Title="深入浅出 Java SSH" },
                 new Book(){ Title=".NET高级编程 第八版" }
             };

             ViewBag.Publishings = publishings;  // 将出版社集合存入ViewBag.Publishings中
             ViewBag.Categories = categories;    // 将图书分类集合存入ViewBag.Categories中

             return View(books); // 返回Book类的强类型视图
         }
     }
 }

图书信息控制器 ~/Controllers/BookController.cs

  在上述控制器的代码中,我们首先模拟数据查询操作,创建了三个集合,分别是图书分类、出版社以及图书信息集合。

  图书分类集合和出版社集合分别存入ViewBag中,方便我们在视图中调用并绑定到下拉框中显示,而图书信息集合则作为View()的参数返回,也就是上面我们介绍过的强类型视图,这样,我们在页面上就可以访问这三个集合数据了。

  这么做当然能够实现我们的目标功能,并且也为在视图中显示数据提供了一种灵活的方法。但是这中方法并不推荐经常使用。

  由于前面已经说到的原因,一般应该坚持使用强类型模型对象,而不是ViewBag和ViewData —— 必须使所有数据都是强类型数据,以便视图编写人员能够利用智能感知功能。

  可能采用的一个方法是编写自定义的视图模型类,这里的视图模型可以看成仅限于向视图提供信息的模型。

  注意这里使用的术语“视图模型”不同于Model View ViewModel(MVVM)模式中的视图模型的概念,这也是我们为什么在介绍视图模型时,使用术语“视图特定模型”的原因所在。

  例如,如果需要一个购物车汇总页面,用来显示商品列表、购物车中商品的总金额以及显示给用户的消息,就可以创建ShoppingCartViewModel类,如下所示:

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Web;

 namespace BookStore.Models
 {
     /// <summary>
     /// 购物车的视图模型
     /// </summary>
     public class ShoppingCartViewModel
     {
         public IEnumerable<Book> Books { get; set; }    // 图书商品
         public decimal TotleMoney { get; set; } // 总金额
         public string Message { get; set; } // 提示消息
     }
 }

购物车的视图模型类 ~/Models/ShoppingCartViewModel.cs

  现在可以使用如下的@model指令,向这个模型中强制性地输入一个视图: @model ShoppingCartViewModel

  这就在不需要改变Model类的情况下带来了强类型视图的益处,其中包括类型检查、智能感知以及免于转换无类型的ViewDataDictionary对象。

  到此,我们已经介绍了一些视图模型相关的概念,后面我们会着重介绍这方面的知识。

7. 添加视图

  前面我们已经介绍了控制器制定视图的基本原理,但是如何创建视图呢?虽然可以手动创建视图文件,然后把它添加到Views对应的目录下,但是Visual Studio中ASP.NET MVC工具的“添加视图”对话框会让我们更方便地创建视图。

  显示“添加视图”对话框最简单的方法就是在Controller控制器中的Action操作方法上右键,使用任何Action操作方法都可以。在这里,我们找到HomeController控制器,创建一个Edit()的操作方法,代码如下:

     public ActionResult Edit()
     {
         return View();
     }

  紧接着,我们在这个方法中的任何位置右键,选择“添加视图”来为当前选择的操作方法Edit()创建一个视图,如图:

ASP.NET MVC 5 05 - 视图

图5 - 右键Edit()、添加视图

  然后打开“添加视图”的对话框如图6所示:

ASP.NET MVC 5 05 - 视图

图6 - 添加视图对话框

  下面介绍一下每个菜单项都是啥玩意儿:

1. 视图名称:当右键一个操作方法选择添加视图时,视图的名称默认就是刚刚右键选择的那个方法的名称。视图名称是必须有的;

2. 模板:一旦选择一个视图类型,就可以选择一个基架模板,这些模板利用Visual Studio模板系统来生成基于选择模型类型的视图,如图7显示了这些模板,其描述见表1:

ASP.NET MVC 5 05 - 视图

图7 - 视图模板类型

表1 - 视图模板类型
模板名称 描述信息
Create 创建一个视图,其中带有创建模型对象的表单,并为模型类的每一个属性生成一个标签和输入框
Delete 创建一个视图,其中带有删除现有模型对象的表单,并为模型对象的每一个属性显示一个标签以及当前该属性的值
Details 创建一个视图,它显示了模型类型的每一个属性的标签及其相应值
Edit 创建一个视图,其中带有编辑现有模型对象的表单
Empty  创建一个空视图,使用@model语法指定模型视图
Empty(不具有模板) 与Empty模板一样,创建一个空视图。但是,由于这个模板没有视图模型,因此在选择此模板时不需要选择模板类型,这是唯一不需要选择模型类型的一个模板 
List 创建一个带有模型类型的视图,为模型类的每一个属性生成一列,确保操作方法向视图传递的是IEmnumberable<ViewModelType> 类型,同时为了执行创建、编辑、删除操作,视图中还包含了指向操作的链接

3. 引用脚本库:这个选项是用来指示要创建的视图是否应该包含指向Javascript库(如果对视图有意义的话)的引用。

默认情况下,_Layout.cshtml文件既不引用jQuery Validation库,也不引用Unobtrusive jQuery Validation库,值引用主jQuery库。

当创建一个包含数据条目表单的视图(如Edit/Create)时,选择这个选项会添加对jqueryval bundle的脚本引用。如果要实现客户端验证,那么这些库就是必须的。除了这种情况以外,完全可以忽略这个复选框。

4.创建为分布视图:选择这个选项意味着要创建的视图不是一个完整的视图,因此,Layout选项是不可用的。

生成的部分视图除了在其顶部没有<html>标签和<head>标签之外,很像一个常规的视图。

5.使用布局页:这个选项决定了要创建的视图是否引用布局页,还是成为一个完全独立的视图。

如果选择使用默认布局页,就没必要指定一个布局了,因为在_ViewStart.cshtml文件中已经指定了默认布局。

这个选项是用来重写默认布局文件的。

  

8. Razor视图引擎

  前面的部分介绍了如何在控制器中指定视图以及如何添加视图。然而这些内容并没有涉及在视图中执行的语法。

  ASP.NET MVC提供了两种不同的视图引擎:较新的Razor视图引擎和较早的Web Forms视图引擎。

  本篇只介绍Razor视图引擎,其中包括Razor语法、布局和部分视图等。

8.1 Razor的概念

  Razor视图引擎是ASP.NET MVC 3中新扩展的内容,并且也是它的默认视图引擎。本篇主要介绍Razor视图引擎,对Web Forms视图引擎就不做过多介绍了。

  Razor是ASP.NET MVC特性团队对收到的强烈请求之一回应的产物,该请求建议提供一个干净、轻量级的、简单的视图引擎,不要包含原有Web Forms视图引擎的“语法累赘”。

  许多开发人员认为编写视图所带来的语法噪音给读取视图造成了很大的阻碍。

  在ASP.NET MVC3中,新引入的Razor视图引擎最终满足了这一请求。

  Razor为视图表示提供了一种精简的语法,最大的限度地减少了语法和额外的字符。这样就有效地减少了语法障碍,并且在视图标记语言中也没有新的语法规则。

  许多编写Razor视图的开发人员都觉得视图代码的编写非常流畅。在Visual Studio中又为Razor添加了一流的只能感知功能,从而使得这种感觉更加明显。

  Razor通过理解标记的结构来实现代码和标记之间尽可能顺畅地转换。下面的一些例子会帮助理解这一点。

  下面这个例子延时了一个包含少量视图逻辑的简单Razor视图:

 @{
     // 这里是代码块,写了一个单行注释。

     // 下面创建了一个数组:
     var items = new[] { "one", "two", "three" };
 }

 <html>
     <head>
         <title>这是标题</title>
     </head>
     <body>
         <h1>遍历数组的 @items.Length 项数据如下: </h1>
         <ul>
             @foreach (var item in items)
             {
                 <li>当前项值为:@item.</li>
             }
         </ul>
     </body>
 </html>

  运行效果如图8所示:

ASP.NET MVC 5 05 - 视图

图8 - 上述示例代码运行结果

  上面的代码示例采用了C#语法,这将意味着这个文件的扩展名是.cshtml。同理,使用Visual Basic语法的Razor视图的扩展名是.vbhtml。

  这些文件扩展名很重要,因为它们指出了Razor语法分析器的编码语言的语法。

不要过多地考虑

  下面详细深入地介绍Razor的语法机制。

  在这之前,强烈建议记住:Razor的设计理念是简单直观。

  对于大多数应用,我们不必关心Razor语法——只需要再插入代码时,输入HTML和@符合即可。

  如果刚刚接触ASP.NET MVC,可以先跳过本章剩余内容,所以即使对于复杂的网站,一般来说对Razor有基本的理解也就足够了。

8.2 代码表达式

  Razor中的核心转换字符是“at”符号@。这个单一字符用做“标记-代码”转换的字符,有时也反过来用做“代码-标记”的转换字符。

  这里共有两种基本类型的转换:代码表达式和代码块。它会先求出表达式的值,然后将值写入到响应中。

  例如,在下面的代码段中:

  <h1>遍历数组的 @items.Length 项数据如下: </h1>

  注意,表达式@items.Length是作为隐式代码表达式求解的,然后在输出中显示表达式的值(3)。

  需要注意的一点是,这里不需要指出代码表达式的结束为止。相比之下,Web Forms视图只支持显示代码表达式,这样,上面的代码段将是如下格式:

  <h1>遍历数组的 <%:items.Length %> 项数据如下: </h1>

  Razor十分只能,可以知道表达式后面的空格字符不是一个有效的标识符,所以它可以顺畅地转回到HTML标记语言。

  注意,在无序列表@item代码表达式中,@后面的字符是一个有效的代码字符(item),所以@会将item作为变量去引用,然后输出结果。

  但是Razor是怎么知道表达是后面的点(@item.)不是要调用item中的属性或方法呢?

  原来Razor是在点字符处向后窥看,看到了一个尖括号(@item.</li>),因此它就知道这不是一个有效的代码标识符,所以到.处就停止向后解析并转回标记模式了。

  

  Razor自动从代码转回HTML标记的能力是其广受欢迎的一个方面,也是其保持语法简洁干净的秘方。

  但是这样一来也带来了一些问题,代码可能会出现潜在的二义性,例如下列的Razor代码:

ASP.NET MVC 5 05 - 视图

  而我们本意是想输出:“一个无奈的错:张董.呵呵。”。

  它呢?却把后面的“呵呵”当做是blogMaster的属性值了。=_=#。

  在这种情况下,Razor肯定不能理解我们的意图,反而会故作聪明地认为@blogMaster.呵呵是一个完整的表达式。不过还好Razor可以这么做:

ASP.NET MVC 5 05 - 视图

  这样,就告诉Razor,“.呵呵”是字面意义上的文本,括号里面的才是代码表达式。

  既然现在是在介绍代码表达式,就应该了解一下显示一个电子邮件地址是的情况。例如: <span>@qq.com</span> 。

  乍一看,貌似没错,但是仔细一瞅,怎么看都像是想输出变量qq里面的com属性。有木有。。

  但是Razor足够智能,它可以辨别出电子邮件地址的一般模式,而不会去解析它为代码表达式。

注意:

  Razor采用了一个简单算法来判断看起来像电子邮件地址的字符串到底是不是一个有效的邮件地址。虽然他不完美,但却可以适用于大多数情况。

  在一些特殊情况下,有效地邮件地址可能会显示不出来,这时可以用两个@@符号来通过转义的形式输出一个@。

  但是,如果确实想这种形式的字符串作为一个表达式该怎么办?例如: <li>Item_@items.Length</li>

  这种情况下。Razor肯定会认为这是一个电子邮件地址。但是我们却想输出结果:“<li>Item_1</li>”,怎么办?!

  忘了最开始的那个括号了嘛~哈哈。 <li>Item_@(items.Length)</li>

  任何时候,Razor的@有了二义性,都可以用括号来表明你的用意~~

  正如前面提到的,可以使用@@来转义@符号。因此,当我们遇到例如话题“@who”这些情况时,我们就可以使用@@who来显示内容了,例如: <span>有人@@我(1条消息)</span> 。

8.3 HTML编码(防XSS)

  因为在许多情况下都需要用视图显示用户输入的内容,如博客评论或留言板等,所以总是存在潜在的跨站脚本注入攻击(XSS,详细的以后的文章将会介绍)。

  值得称赞的是Razor表达式是用HTML自动编码的。

 @{
     string message = "<script>alert('Hacked By GuYing!');</script>";
 }
 <span>@message</span>

  上述这段代码不会弹出一个警告对话框,而是将message变量进行HTML编码变成了:

     &lt;script&gt;alert('Hacked By GuYing!');&lt;/script&gt; 。

  因此上述代码的输出结果为:

ASP.NET MVC 5 05 - 视图

  然而,如果想展示HTML代码,就返回一个System.Web.IHtmlString对象的实例,Razor并不会对它进行编码。

  例如,稍后我们将要学习的所有视图辅助类都是返回这个接口的实例,因为它们想在页面上呈现HTML。

  当然也可以创建一个HtmlString实例或者使用Html.Raw便捷方法:

 @{
     string message = "<script>alert('Hacked By GuYing!');</script>";
 }
 <span>@(Html.Raw(message))</span>

  这样就会显示不经过HTML编码的数据:

ASP.NET MVC 5 05 - 视图

  虽然这种自动的HTML编码通过对一下HTML形式显示的用户输入进行编码有效地缓和了XSS的脆弱性,但是对于在JavaScript中显示用户输入来说还是不够的。

  例如:

 <h1 id="message"></h1>

 <script type="text/javascript">
     $(function () {
         var message = "Hello, @(ViewBag.Username)";
         $("#message").html(message).show();
     });
 </script>

  在这段代码中,将一个字符串赋给了JavaScript变量message,而且该字符串中包含了用户通过Razor表达式提供的用户名。

  通过jQuery的HTML方法,变量message将被设置为一个ID属性值为“message”的DOM元素。

  尽管在message字符串中对用户进行了HTML编码,但是仍然具有潜在的XSS脆弱性。

  例如,如果用户提供一下的字符串作为用户名,HTML将被设置为一个脚本标签:

 \x3cscript\x3e%20alert(\x27XSS!\x27)%20\x3c/script\x3e

  当在JavaScript中将用户名提供的值赋给变量时,要使用JavaScript字符串编码而不仅仅是HTML编码,记住这一点是很重要的。

  也就是要使用@Ajax.JavaScriptStringEncode方法对用户输入进行编码。

  下面是使用了这个方法的相同代码,这样就可以有效地避免XSS攻击:

 <h1 id="message"></h1>

 <script type="text/javascript">
     $(function () {
         var message = "Hello, @(Ajax.JavaScriptStringEncode(ViewBag.Username))";
         $("#message").html(message).show();
     });
 </script>

注意:

  理解HTML和JavaScript编码的安全隐患是很重要的。不正确的编码会使网站和用户存在危险境地。

  这些内容咱们后面涉及到了再细谈吧。

8.4 代码块

  Razor在视图中除了支持代码表达式以外,还支持代码块。

  回顾前面的视图示例,其中有一条foreach语句:

 @foreach (Category item in categories)
 {
     <li>@item.Title</li>
 }

  这段代码迭代了一个数组,并为数组中的每一项显示了一个列表项li元素。

  这个语句好玩的地方是,foreach语句会自动转换为带有起始<li>标签的标记。

  当看到这段代码时,人们有时可能会假定是换行符号导致了这个转换的发生,但是下面有效的代码片段证实了情况并非如此:

 @foreach (Category item in categories) { <li>@item.Title</li> }

  因为Razor理解HTML标记语言的结构,所以当<li> 标签关闭时它也可以自动地转回代码。

  因此,这里不需要划定右花括号。

  相比之下,对于同样用于代码和标记之间转换的代码,Web Forms视图引擎就不得不显式地指出,如下所示:

 <% foreach (Category item in categories) { %>
      <li><%:item.Title %></li>
 <% } %>

  代码块除了需要@符号分割之外还需要使用花括号。下面是一个多行代码块的例子:

 @{
     string s = "Welcome to my Blog!";
     ViewBag.Title = "Index";
 }

  另一个例子是当调用没有返回值的方法(也就是返回类型为void)时:

 @{Html.RenderPartial("SomePartial");}

  注意代码块中的语句(比如foreach循环和if代码块中的语句)是不需要使用花括号的,因为Razor引擎是理解这些C#关键字的专门知识的。 这就是咱们.NET的强大。呵呵。

8.5 Razor语法示例

  本节通过示例来说明常见用途下的Razor语法。

1. 隐式代码表达式

之前说到,代码表达式将被计算 并将值写入到响应中,这就是在视图中显示值的一般原理。
 <span>@model.Message</span> 
Razor中的隐式代码表达式总是采用HTML编码方式。

2. 显式代码表达式

代码表达式的值将被计算并写入到响应中,这就是在视图中显示值的一般原理。
 <span>1+2 = @(1+2)</span>

3. 无编码代码表达式

有些情况下,需要显式地渲染一些不应该采用HTML编码的值,这时可以采用@Html.Raw()方法来保证该值不被编码。
 <span>@(Html.Raw(model.Message))</span>

4. 代码块

不像代码表达式那样,先求得表达式的值,然后再输出到响应,代码块是简单地执行代码部分。
这一点对于声明以后要使用到的变量是有帮助的。

 @{
     ;
     string y = "Welcome to My Blog.";
 }

5. 文本和标记相结合

这个例子显示了再Razor中混用文本和标记的概念,具体如下:

 @foreach(var item in items){
     <span>Item @(item.Name).</span>
 }

6. 混合代码和纯文本

Razor查找标签的开始位置以确定何时将代码转换为标记。
然而,有时可能想在一个代码块之后立即输出纯文本。
例如,在下面的这个例子中就是展示如何在一个条件语句块中显示纯文本:

 @if(showMessage){
     <text>Welcome to my Blog!</text>
 }

或:

 @if(showMessage){ @:Welcome to my Blog!
 }

注意Razor可以采用两种不同的方式来混合代码和纯文本。

第一种方式是使用<text>标签没这样只是把标签内容写入到响应中,而标签本身则不写入。
这种方式的优点是它具有逻辑意义,如果想转回标记,只需要使用一个标签就行了。

第二种方式是使用一个特殊的语法,来实现从代码到纯文本的转换,但是这种方法每次只能作用于一行文本。

7. 转移代码分隔符

本篇之前说到,可以用“@@”来编码“@”以达到显示“@”的目的。
此外,始终都可以选择使用HTML编码来实现:
Razor:
 My Email &#;qq.com. 
或:
 My Email @@qq.com.

8. 服务器端的注释

Razor为注释一块代码和标记提供了美观的语法:

 @*
     Welcome to my Blog!
     @if(showMessage){
         <h1>@ViewBag.Message</h1>
     }
 *@

9. 调用泛型方法

这与显式代码表达式相比基本上没有什么不同。即便如此,在试图调用泛型方法时仍有很多困惑。
困惑的原因主要在于调用泛型方法的代码包含尖括号。

尖括号会导致Razor转回标记,除非整个表达式用圆括号括起来。Razor和Web Forms中的泛型使用对比如下:
 @(Html.SomeMethod<AType>())

8.6 布局

Razor的布局有助于使应用程序中的多个视图保持一致的外观。
如果熟悉Web Forms的话,其中母版页和布局的作用是相同的,但是布局提供了更加简洁的语法和更大的灵活性。

可使用布局为网站定义公共模板(或只是其中的一部分)。公共模板包含一个或多个占位符,应用程序中的其他视图为它(占位符)提供显示的具体内容。
从某些角度来看,布局很像视图的抽象基类。视图中的定义就类似于重写基类中的定义。

下面来看一个非常简单地布局。这里称这个布局文件为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 = "Index Page";
}
<p>Welcome to my Blog!</p>

上面这个视图通过Layout属性来指定布局。

当渲染这个视图时,它的HTML内容将被放在SiteLayout.cshtml中的id属性值为“main-content”的DIV元素中,最后生成HTML标记如下所示:

 <!DOCTYPE html>
 <html>
   <head>
     <title>Index Page</title>
   </head>
   <body>
     <h1>Index Page</h1>
     <div id="main-content"><p>Welcome to my Blog!</p></div>
   </body>
 </html>

注意视图内容,其中标题和h1标记内容都是由视图Index.cshtml提供的。
除此之外的所有其他内容都是由布局SiteLayout.cshtml提供的。

布局可能有多个节点。例如,下面示例在前面的布局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>

在不做任何改变的情况下再次运行前面的视图,将会抛出一个异常,提示Index.cshtml视图页面没有定义Footer节点。
默认情况下,视图必须为布局中定义每一个节点提供相应的内容。
这是更改之后的Index.cshtml视图,如下所示:

 @{
   Layout = "~/Views/Shared/SiteLayout.cshtml";
   ViewBag.Title = "Index Page";
 }
 <p>Welcome to my Blog!</p>
 @section Footer{
   CopyRight &copy; 张董丶
 }

@section语法为布局中定义的一个节点指定了内容。

刚才指出:默认情况下,视图必须为布局中定义的每一个节点提供内容。
那么当向一个布局视图中要添加一个新节点时,难道已经创建好的每一个视图页面都无法正常运行吗?(因为没有@section实现)

然而@RenderSection()方法有一个重载版本,允许指定不需要的节点。可以给required参数传递一个bool类型的值来标记当前节点是否可选:
 <footer>@RenderSection("Footer", required:false)</footer>

换一个角度看,加了required的节点定义,很像基类中的virtual虚方法。子类可以实现也可以不实现。
而不添加required节点,很像基类的abstract抽象方法。子类必须具有具体的实现。

既然加了required的节点相当于virtual虚方法,那么如果视图没有提供该节点内容,布局页是不是也能提供默认实现呢?具体实现如下:

 @if(IsSectionDefined("Footer"))
 {
     RenderSection("Footer");
 }
 else
 {
     <span>This is default 'Footer' content!</span>
 }

  后面我们会介绍Razor语法的一个高级特性,称为模板Razor委托,利用它可以实现一个更好的方法来解决这个问题。

MVC5中默认的布局变化
  当使用Internet或Intranet模板创建一个新的MVC5应用程序时,我们会得到一个使用Bootstrap框架应用基本样式的默认布局。

  默认的布局设计在这些年成熟了不少,在MVC4之前,默认模板的设计非常Spartan——在蓝色的背景上只有一片白色区域。
  MVC4对默认的模板进行了彻底的重新编写,提供了更好的可视化设计,并通过CSS媒体查询(CSS Media Queries)提供了自适应的设计。

  这是一个极大的改进,但是用到的只是自定义的HTML和CSS。
  正如开始所说,默认的模板已被更新,使用了流行的Bootstrap框架。
  它具有MVC4模板更新的一些好处,但是也添加了更多的好处。后续的文章中我们将对这些内容进行详细介绍。

8.7 ViewStart  

  在前面的例子中,每一个视图都是使用Layout属性来指定它的布局。

  如果多个视图使用同一个布局,就会产生冗余,并且很难维护。

  _ViewStart.cshtml页面可以用来消除这种冗余,这个文件中的代码先于同目录下任何视图代码的执行。
这个文件也可以递归应用到子目录下的任何视图。

  当创建一个默认的ASP.NET MVC项目时,你将会注意到在Views目录下会自动添加一个_ViewStart.cshtml文件,它指定了一个默认布局:

 @{
     Layout = "~/Views/Shared/_Layout.cshtml";
 }

  以为这个代码先于任何视图运行,所以一个视图可以重写Layout属性的默认值,从而重新选择一个不同的布局。

  如果一组视图拥有共同的设置,那么_ViewStart.cshtml文件就有了用武之地,因为我们可以在它里面对共同的视图配置进行统一设置。
  如果有视图需要覆盖统一的设置,我们只需要修改对应的属性值即可。

9.指定部分视图

  除了返回视图之外,控制器中的操作方法也可以通过PartialView方法以PartialViewResult的形式返回部分视图。

  下面是一个例子:

 public class HomeController : Controller{
     public ActionResult Message(){
         ViewBag.Message = "Welcome to my Blog!";
         return PartialView();
     }
 }

  这种情形下,渲染的视图Message.cshtml,但是如果布局是由_ViewStart.cshtml页面指定(而不是直接在视图中)的,将无法渲染布局。

  除了不能指定布局之外,部分视图看起来和正常视图没有分别,下面是部分视图Message.cshtml的代码:

 <h2>@ViewBag.Message</h2>

  在使用Ajax技术进行部分更新时,部分视图是很有用的。

  下面展示了一个非常简单的例子,使用jQueyr将一个部分视图的内容加载到一个使用了Ajax调用的当前视图中:

 <div id="result"></div>

 @section Scripts {
     <script>
         $(function(){
             $("#result").load("/home/message");
         });
     </script>
 }

  前面的代码使用jQuery的load方法想Message操作方法发送一个Ajax请求,而后使用请求的结果更新id属性值为result的DIV元素。

10.小结

  视图引擎的用途非常具体有限,他们的目的是获取从控制器传递给它们的数据,并生成经过格式化的输出,通常是HTML格式。

  除了这些简单的职责或“关注点”之外,作为开发人员,还可以以任何想要的方式来实现视图的目标。

  Razor视图引擎简单直观的语法使得编写丰富安全的页面变得极其容易,而不必考虑编写页面的难易程度。