ASP.NET MVC 4 (五) 视图

视图引擎与视图

多数情况下控制器action方法返回ViewResult对象,MVC内建action调用器ControllerActionInvoker负责调用控制器action方法并调用视图引擎处理ViewResut,由视图引擎将ViewResult转化为ViewEngineResult对象,ViewEngineResult对象内含实现IView接口的视图对象,最终MVC框架调用视图对象的Render的方法渲染输出结果。下面还是以例子来演示这个过程,先来看看相关接口和类的定义与实现:

public interface IViewEngine {
  ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
  ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
  void ReleaseView(ControllerContext controllerContext, IView view);
} 

public interface IView {
  void Render(ViewContext viewContext, TextWriter writer);
} 

public class ViewEngineResult {
  public ViewEngineResult(IEnumerable<string> searchedLocations) {
    if (searchedLocations == null) {
      throw new ArgumentNullException("searchedLocations");
    }
    SearchedLocations = searchedLocations;
  }
  public ViewEngineResult(IView view, IViewEngine viewEngine) {
    if (view == null) { throw new ArgumentNullException("view");}
    if (viewEngine == null) { throw new ArgumentNullException("viewEngine");}
    View = view;
    ViewEngine = viewEngine;
  }
  public IEnumerable<string> SearchedLocations { get; private set; }
  public IView View { get; private set; }
  public IViewEngine ViewEngine { get; private set; }
} 

IViewEngine的两个Find方法查找请求的视图返回ViewEngineResult对象,ViewEngineResult有两个函数,一个接受IView和IViewEngine作为参数,另一个传入一系列视图文件搜索路径列表作为参数。

先从自定义视图类开始:

public class DebugDataView : IView {

        public void Render(ViewContext viewContext, TextWriter writer) {
            Write(writer, "---Routing Data---");
            foreach (string key in viewContext.RouteData.Values.Keys) {
                Write(writer, "Key: {0}, Value: {1}",
                    key, viewContext.RouteData.Values[key]);
            }

            Write(writer, "---View Data---");
            foreach (string key in viewContext.ViewData.Keys) {
                Write(writer, "Key: {0}, Value: {1}", key,
                    viewContext.ViewData[key]);
            }
        }

        private void Write(TextWriter writer, string template, params object[] values) {
            writer.Write(string.Format(template, values) + "<p/>");
        }
    }

DebugDataView只是简单的输出一些路径映射和视图数据。接下来是自定义视图引擎:

public class DebugDataViewEngine : IViewEngine {

        public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) {

            if (viewName == "DebugData") {
                return new ViewEngineResult(new DebugDataView(), this);
            } else {
                return new ViewEngineResult(new string[]
                    { "No view (Debug Data View Engine)" });
            }
        }

        public ViewEngineResult FindPartialView(ControllerContext controllerContext,string partialViewName, bool useCache) {

            return new ViewEngineResult(new string[]
                { "No view (Debug Data View Engine)" });
        }

        public void ReleaseView(ControllerContext controllerContext, IView view) {
            // do nothing
        }
    }

DebugDataViewEngine中最主要的是FindView方法,如果当前请求的是DebugData视图,我们直接创建一个DebugDataView并以它构建ViewEngineResult返回,其他情况下返回一个包含虚假搜索路径的ViewEngineResult(真实实现的话我们需要搜索模板文件等)。要使用自定义的视图引擎我们还需要在App_start中注册:

ViewEngines.Engines.Add(new DebugDataViewEngine()); 

在一个应用可以注册多个视图引擎,action调用器依次调用这些视图引擎的FindView方法,一旦某一个搜索引擎返回包含IView对象的ViewEngineResult结果调用停止,所以视图引擎注册的先后顺序是有影响的,可能存在两个视图引擎都可以处理同一个视图名称。如果我们想自定义的视图引擎优先处理可以将其插入列表首位:

ViewEngines.Engines.Insert(0, new DebugDataViewEngine());

如果某个action方法返回DebugData视图,比如:

return View("DebugData");

最后的结果就是调用DebugDataView.RenderData输出结果。如果我们请求一个未实现的视图,得到的结果就是:

错误显示一系列视图模板的搜索路径,包含DebugDataViewEngine给出的虚假路径"No view (Debug Data View Engine)"。结果中其他一些路径来自于默认的Razor和ASPX视图引擎,你可以调用ViewEngines.Engines.Clear()清除默认视图引擎后仅注册自定义的视图引擎。

简单总结上面的示例可以说视图引擎完成从视图名称到视图对象的转换,而视图对象则负责具体的输出响应。

Razor视图引擎

只有极少数情况下我们需要自定义视图引擎,MVC已经为我们提供了Razor和ASPX引擎,Razor在MVC3中引入用以替代ASPX引擎,所以推荐使用Razor引擎。Razor引擎处理的是.cshtml视图文件,一个简单的Index.cshtml:

@model string[]
@{ Layout = null; 
ViewBag.Title = "Index";
}
This is a list of fruit names:
@foreach (string name in Model) {
<span><b>@name</b></span>
}

在启动应用程序后,Razor引擎将cshtml文件转换为c#类的定义,我们可以在C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root下找到这些临时文件,比如上面的index.cshtml转成c#的.cs文件可能是这样的:

namespace ASP {
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Helpers;
using System.Web.Security;
using System.Web.UI;
using System.Web.WebPages;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Optimization;
using System.Web.Routing;
  public class _Page_Views_Home_Index_cshtml : System.Web.Mvc.WebViewPage<string[]> {
    public _Page_Views_Home_Index_cshtml() {
    }
    public override void Execute() {
      ViewBag.Title = "Index";
      WriteLiteral("\r\n\r\nThis is a list of fruit names:\r\n\r\n");
      foreach (string name in Model) {
        WriteLiteral(" <span><b>");
        Write(name);
        WriteLiteral("</b></span>\r\n");
      }
    }
  }
} 

它从 WebViewPage<T>扩展,T是视图数据模型类型,上面的例子string[]。理解Razor如何处理cshtml有助于我们后续理解html帮助函数是如何工作的。

当我们请求一个视图比如Index时,Razor引擎遵循约定规则在下列路径查找视图文件:

•  ~/Views/Home/Index.cshtml
•  ~/Views/Home/Index.vbhtml
•  ~/Views/Shared/Index.cshtml
•  ~/Views/Shared/Index.vbhtml

实际上Razor并非真正的搜索这些磁盘文件,因为这些模板已经编译为c#类。RazorViewEngine的下列属性和模板搜索相关:

属性   默认值

ViewLocationFormats 
MasterLocationFormats 
PartialViewLocationFormats

搜索视图、部分视图和布局文件
~/Views/{1}/{0}.cshtml, 
~/Views/{1}/{0}.vbhtml, 
~/Views/Shared/{0}.cshtml, 
~/Views/Shared/{0}.vbhtml


AreaViewLocationFormats 
AreaMasterLocationFormats 
AreaPartialViewLocationFormats

区域搜索视图、部分视图和布局文件
~/Areas/{2}/Views/{1}/{0}.cshtml, 
~/Areas/{2}/Views/{1}/{0}.vbhtml, 
~/Areas/{2}/Views/Shared/{0}.cshtml, 
~/Areas/{2}/Views/Shared/{0}.vbhtml

这里{0}表示视图名称,{1}表示控制器名称,{2}标识区域名称。我们可以子类化RazorViewEngine后修改上述属性更改Razor的搜索方式,当然必须注册子类化的视图引擎到引擎列表。

Razor模板文件

Razor模板文件混合了HTML和C#的语句,以一个例子来具体分析:

@model Razor.Models.Product
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
@Model.Name
</div>
</body>
</html> 

第一行@model Razor.Models.Product 指定了视图的模型对象类型,后续我们可以使用@Model来引用该对象(注意M大写)。模型对象类型不是必须的,视图文件中完全可以没有这行,带模型类型的视图我们称之为强类型视图(Strong typed)。

第二行以“@{”开始一个Razor代码块,类似C#的代码块,最后也要以“}”结尾。

“Layout = null;”表示视图不使用布局文件,布局文件存放在View\Layouts目录下,可以为多个视图文件共享。布局文件名一般以下划线“_”开始,比如_BasicLayout.cshtml,以下划线开头的不会返回给用户,这样可以帮助我们区分哪些是支持文件。布局文件其实也是一个Razor模板文件,比如:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<h1>Product Information</h1>
<div style="padding: 20px; border: solid medium black; font-size: 20pt">
@RenderBody()
</div>
<h2>Visit <a href="http://apress.com">Apress</a></h2>
</body>
</html> 

最重要的是这里的 @RenderBody()(后面我们知道称为HTML帮助函数),它的作用是将视图的渲染结果插入到这里。使用布局文件后视图可以简化为:

@model Razor.Models.Product
@{
ViewBag.Title = "Product Name";
Layout = "~/Views/_BasicLayout.cshtml";
}
Product Name: @Model.Name 

实际上我们不需要在每个视图文件中指定Layout,MVC会搜索一个名为 _ViewStart.cshtml的文件,它的内容会自动插入到所有视图文件中,所以如果我们要为所有视图文件指定布局文件可以在 _ViewStart.cshtml中定义:

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

Razor语法

我们可以很方便的在视图中插入视图模型数据或者ViewData的数据:

@model Razor.Models.Product
@{
ViewBag.Title = "DemoExpression";
}
<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> 

这些值也可以很方便的应用到标记属性上:

<div data-discount="@ViewBag.ApplyDiscount" data-express="@ViewBag.ExpressShip" data-supplier="@ViewBag.Supplier">
The containing element has data attributes
</div>
Discount:<input type="checkbox" checked="@ViewBag.ApplyDiscount" />
Express:<input type="checkbox" checked="@ViewBag.ExpressShip" />
Supplier:<input type="checkbox" checked="@ViewBag.Supplier" /> 

可以使用条件语句:

<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> 

注意“@:Out of Stock ”一行的“@:”,它阻止Razor将后续语句解释为代码。上面的switch换成if:

<td>
@if (ViewBag.ProductCount == 0) {
@:Out of Stock
} else if (ViewBag.ProductCount == 1) {
<b>Low Stock (@ViewBag.ProductCount)</b>
} else {
@ViewBag.ProductCount
}
</td> 

枚举数据:

@model Razor.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 (Razor.Models.Product p in Model) {
<tr>
<td>@p.Name</td>
<td>[email protected]</td>
</tr>
}
</tbody>
</table>
} else {
<h2>No product data</h2>
} 

在引用数据类型时我们用了完整的命名空间,可以将命名空间如果c#一样using引入:

@using Razor.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 (Productp in Model) {
<tr>
<td>@p.Name</td>
<td>[email protected]</td>
</tr>
}
</tbody>
</table>
} else {
<h2>No product data</h2>
} 

添加动态内容到Razor视图

除了静态的HTML,我们可以在视图模板中嵌入动态内容,动态内容在运行时输出,比如上面的内联@if、@Model等;还可以嵌入HTML帮助函数,比如布局文件中用到的@RenderBody()。除此之外我们还可以嵌入节(Sections)、分部视图(Partial views)和子动作(Child actions )。

  • 使用节的例子
@model string[]

@{
    ViewBag.Title = "Index";
}

@section Header {
    <div class="view">
        @foreach (string str in new [] {"Home", "List", "Edit"}) {
            @Html.ActionLink(str, str, null, new { style = "margin: 5px" })
        }
    </div>
}

@section Body {
    <div class="view">
        This is a list of fruit names:

        @foreach (string name in Model) {
            <span><b>@name</b></span>
        }
    </div>
}

@section Footer {
    <div class="view">
        This is the footer
    </div>
}

这里定义了Header、Body、Footer三个节,我们可以在布局文件中引用这些节:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <style type="text/css">
        div.layout { background-color: lightgray;}
        div.view { border: thin solid black; margin: 10px 0;}
    </style>
    <title>@ViewBag.Title</title>
</head>
<body>
    @RenderSection("Header")

    <div class="layout">
        This is part of the layout
    </div>

    @RenderSection("Body")

    <div class="layout">
        This is part of the layout
    </div>

    @if (IsSectionDefined("Footer")) {
        @RenderSection("Footer")
    } else {
        <h4>This is the default footer</h4>
    }

    @RenderSection("scripts", false)

    <div class="layout">
        This is part of the layout
    </div>
</body>
</html>

注意@RenderSection("scripts", false)多了个参数false,其作用是表示scripts节可选的,如果视图中没有定义scripts节则不需要输出。如果这里不加这个false参数Razor会提示节未找到错误。

  • 使用分部视图的例子

分部视图可以在添加视图窗口中选中“Create as partial view”创建 - MyPartial.cshtml:

<div>
This is the message from the partial view.
@Html.ActionLink("This is a link to the Index action", "Index")
</div> 

我们在另一个视图文件中引用它:

@{
ViewBag.Title = "List";
Layout = null;
}
<h3>This is the /Views/Common/List.cshtml View</h3>
@Html.Partial("MyPartial") 

分部视图也可以是强类型的:

@model IEnumerable<string>
<div>
This is the message from the partial view.
<ul>
@foreach (string str in Model) {
<li>@str</li>
}
</ul>
</div> 

在引用时传入相应的模型对象:

@{
ViewBag.Title = "List";
Layout = null;
}
<h3>This is the /Views/Common/List.cshtml View</h3>
@Html.Partial("MyStronglyTypedPartial", new [] {"Apple", "Orange", "Pear"}) 
  • 使用子动作的例子

子动作调用的是一个控制的action方法,我们先定义一个这样一个action方法:

public class HomeController : Controller {
...
[ChildActionOnly]
public ActionResult Time() {
return PartialView(DateTime.Now);
}
} 

注意这里使用了ChildActionOnly标识这个action方法,表示仅用于被调用而不能作为标准Action方法访问。它对应一个分部视图:

@model DateTime
<p>The time is: @Model.ToShortTimeString()</p> 

我们在视图中引用它:

@{
ViewBag.Title = "List";
Layout = null;
}
<h3>This is the /Views/Common/List.cshtml View</h3>
@Html.Partial("MyStronglyTypedPartial", new [] {"Apple", "Orange", "Pear"})
@Html.Action("Time") 

如果要调用的子动作不在同一个控制器,我们还需要指定其控制器:

...
@Html.Action("Time", "MyController")
... 

如果子动作有参数,调用时我们也可以指定参数:

...
@Html.Action("Time", new { time = DateTime.Now })
... 

以上为对《Apress Pro ASP.NET MVC 4》第四版相关内容的总结,不详之处参见原版 http://www.apress.com/9781430242369

时间: 2024-08-03 19:20:25

ASP.NET MVC 4 (五) 视图的相关文章

ASP.NET MVC 过滤器(五)

ASP.NET MVC 过滤器(五) 前言 上篇对了行为过滤器的使用做了讲解,如果在控制器行为的执行中遇到了异常怎么办呢?没关系,还好框架给我们提供了异常过滤器,在本篇中将会对异常过滤器的使用做一个大概的讲解. ASP.NET MVC过滤器 过滤器在系统框架中的整体对象模型 IAuthorizationFilter授权认证过滤器的执行过程 使用IAuthorizationFilter过滤器 IActionFilter行为过滤器的执行过程 自定义实现IActionFilter行为过滤器 异常过滤器

ASP.NET MVC路由(五)

ASP.NET MVC路由(五) 前言 前面的篇幅讲解了MVC中的路由系统,只是大概的一个实现流程,让大家更清晰路由系统在MVC中所做的以及所在的位置,通过模糊的概念描述.思维导图没法让您看到路由的实际运用,特此篇幅来说明一下简单的示例. 路由的命名空间的定义 对于路由当中url规则.默认值.url参数约束这些的定义网上都有,本篇讲一下路由中命名空间的定义. 大家都知道路由的作用,让请求匹配到合理的控制器名称,并且交由控制器工厂来生成控制器来执行请求.然而在项目中难免会命名出相同名称的控制器,我

学习笔记:Asp.Net MVC更新部分视图实现

Asp.Net MVC 更新部分视图实现 设想我们有一篇文章下面的提交评论时如何只刷新评论内容部分, 方法一,利用ajax通过js代码实现. 方法二,利用Ajax.BeginForm()+部分视图实现. 通过js代码的方法比较常见,这里主要是探讨通过Ajax.BeginForm()+部分视图的方式实现.这样还可提高该视图的重用性.同理,有多处引用的零部件或页面部分内容更新等,也可以利用此方法实现. Step1,创建生成Form的Action,代码如下: [ChildActionOnly()] p

ASP.NET MVC学习之视图篇(2)

继ASP.NET MVC学习之视图(1)学习 4.HTML辅助器 虽然在ASP.NET MVC中我们已经摆脱了ASP.NET的控件,但是对于页面中需要循环标签的情况依然还是存在,可能很多人认为用foreach就可以完成,但是这个仅仅只是针对单个循环,如果多个循环中都要使用到同样的标签呢?下面笔者就介绍两种方式让我们事半功倍. 首先是针对单个页面的内联辅助器,如果我们遇到只要在单个页面中不断使用的标签的时候,这个方式非常的轻便,比如下面的代码根据文本内容和样式类生成li标签的辅助器(Views/H

转载:ASP.NET MVC扩展自定义视图引擎支持多模板&amp;动态换肤skins机制

ASP.NET mvc的razor视图引擎是一个非常好的.NET MVC框架内置的视图引擎.一般情况我们使用.NET MVC框架为我们提供的这个Razor视图引擎就足够了.但是有时我们想在我们的项目支持多模板&skins机制,比如我们可能会有多套的模板,也就是多个View风格,而我们只需要改一下配置文件就可以轻松的改变页面的风格和模板.实现这个功能有两种方式: 一.使用接口IViewEngine自己完成一个类似Razor视图引擎的功能. 二.继承类RazorViewEngine类,重写它的一些方

ASP.NET MVC学习之视图篇(1)

一.前言 不知道还有多少读者从第一篇开始一直学习到如今,笔者也会一直坚持将ASP.NET MVC的学习完美的结束掉,然后开始写如何配合其他框架使用ASP.NET MVC的随笔.当然笔者后面的随笔如果没有特殊说明使用的都是ASP.NET MVC 4,因为笔者认为只要精通即可. 二.正文 1.自定义视图引擎 相信很多人都知道在控制器中一个动作方法返回一个View之后,ASP.NET MVC默认会到Views下对应的控制器名的文件夹下寻找和这个动作方法同名的视图(如果你指定了视图名则会按照你指定的视图

Asp.net MVC 移除视图引擎(WebFormViewEngine或者RazorViewEngine)

ASP.NET MVC 有两种视图引擎,分别asp.net语法的:WebFormViewEngine 与Razor视图的新语法:RazorViewEngine. 在MVC默认查找视图时,会按照指定的顺序查找,进行匹配视图,当我们的MVC程序未找到视图时,页面中会出现错误信息: 看到图片由可可知,它会先执行webfrom视图引擎查找,其次才是razor视图引擎,而现在ASP.NET MVC 项目通常只所有一种视图引擎就 已经足够了.如果是这样的话,另外一种视图引擎显得多余了,(里面多少也会给性能带

关于 ASP.NET MVC 中的视图生成

在 ASP.NET MVC 中,我们将前端的呈现划分为三个独立的部分来实现,Controller 用来控制用户的操作,View 用来控制呈现的内容,Model 用来表示处理的数据. 从控制器到视图 通常,在 Controller 中,我们定义多个 Action ,每个 Action 的返回类型一般是 ActionResult,在 Action 处理的最后,我们返回对于视图的调用. public ActionResult Index() { return this.View(); } 默认情况下,

Asp.Net Mvc 控制器与视图的数据传递

数据传递也就是控制器和视图之间的交互,比如在视图中提交的数据,在控制器怎么获取,或者控制器从业务层获得一些数据,怎么传递到视图中,让视图显示在客户端呢?带着这些疑问,我们接着看..        下面分别讲解asp.net mvc框架中有三种数据传递方式,分别是:ViewData,TempData,Model         ViewData:只能在一个动作方法中设置,在相关视图页面读取,说得再白一点就是只能在一个页面中使用.         例 在Act ion方法中设置:ViewData["