ASP.NET MVC 2.0 高级编程{译}之--视图
时间:2011-05-29 来源:The Lake House
首先声明,在下英语水平不甚熟练(虽6级已过),因需要ASP.NET MVC 2.0 视图知识,借助Lingoes工具,翻了视图一章,只为英语水平在在下之下的同志做参考,但鼓励看原版。
从表面看,web应用从请求到输出是黑箱操作:输入URL,输出Html页面。路由,控制器和动作是MVC内部机制的重要部分,但是如果不产生HTML它们则是没有任何用处的。在
MVC架构中,视图对输出事关重大。
你已经在很多示例程序中看到过视图,你对它们做了什么也有了粗略的认识。现在就要关注且弄清楚这部分知识。能过阅读本章,你将会学到
- .aspx, 内联代码块,自动HTML编码在场景中是如何工作的。
- 该框架内置的HTML helper方法
- 怎样去创建可重用的视图片段(partials) ,和为它们传值方法不同方法。
视图怎样和ASP.NET MVC配合
大部分软件开发人员懂得最好是把UI代码和业务逻辑代码分开。否则,表现逻辑和应用逻辑将会纠缠在一起,追踪记录它们中的任一个都变成是不可能的。细微的改动可以轻易的引发大量分散bug的产生,然后生产力挥发了。MVC架构为解决这个问题,使视图保持独立,并强制的使它们简易。对于Web应用程序,视图只是用于控制器的输出,并使用间单化的表示逻辑来完成HTML的渲染。
然而,表现逻辑和业务逻辑的界线是主观的。如果你想要创建一个有交换灰颜色行的表格,它有可能是表现逻辑。但是如果你要高亮一个起过某一定数额的数字或是要隐藏一个国定假日呢?你或许会陷入争论-它有可能是一个业务规则,或者只是一个呈现功能,但是你不得不去选择。随着经验的积累,你能决定什么样的复杂程度你会发现在视图逻辑是可以接受的,或者必须把它们放到一个控制器或一个独立的组件能使你进行单元测试。
视图逻辑不如控制器逻辑那样可单元测试,因为视图输出文本而不是结构化的对象。因为这个原因。视图基本上不能进行单元试。
为视图添加内容
一个视图页完全可能只包括固定的,文字的HTML(加上一个<%@ Page %> 声明)
<%@ Page Inherits="System.Web.Mvc.ViewPage" %>
This is a <i>very</i> simple view.
你将会立即学到<%@ Page%> 声明。除去这些,上页的视图只中一些普通的旧的HTML。当然你也可以猜测这将会给予什么到浏览器。这个视图没有生成一个标记完好了HTML文档,它不包含<html>或<body>标记,但是Web Forms视图引擎不知道或关心这个,它乐于提交任何字符串。
添加动态内容到视图的五种方法
如果只是使用动态的HTML,你不会走的很远。你是在编写web业务应用程序。所以你需要一些代码使你的视图变为动态的。 MVC框架为向视图添加动态内容提供了一些机制。从快速且简单的到广泛且强大的,每次添加动态内容的方法将由你说了算。
使用内嵌代码:
假设你有一个视图页叫做ShowPerson.aspx,想要把一个Person类型的对象传入,如下面这样定义
Public class Person
{
Public int PersonId{get; set;}
Public string Name{get; set}
Public int Age{get; set ;}
Public ICollection<Person> Children{get; set;}
}
为了方便起见,当创建视图时,你或许会通过”View data class”选择一个把ShowPerson.aspx标记为一个强类型视图。
现在ShowPerson.aspx可以使用内嵌代码呈现一个Person类型的模型属性
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<YourNamespace.Person>"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title><%: Model.Name %></title>
</head>
<body>
<h1>Information about <%: Model.Name %></h1>
<div>
<%: Model.Name %> is
<%: Model.Age %> years old.
</div>
<h3>Children:</h3>
<ul>
<% foreach(var child in Model.Children) { %>
<li>
<b><%: child.Name %></b>, age <%: child.Age %>
</li>
<% } %>
</ul>
</body>
</html>
为什么内嵌代码在MVC视图里是一个好东西
内嵌代码在ASP.NET Web Forms是不受欢迎的因为Web Forms窗体页都应该代表一个服务器控件层次结构,而且不是一个HTML页。Web Forms是创建Windows窗体的错觉-GUI方式的开发,如果你使用了内嵌代码,你就会破坏掉错觉并且把所有人的游戏损坏。
但对于MVC 框架却不一样。它把web应用开发当作专门的权利,它不会去尝试模仿创建桌面应用的感受,所以它不需要保持任何这样的伪装。HTML是文本,并且用模板生成文本是很容易的事。这些年来许多的web开发平台来来去去,但是在不同的forms使用模板生成HTML却是不变的。这对于HTML很自然,并且工作的相当不错。
我理解你或许会问自已,“但是关注点分离是什么东西,我是否应该把逻辑和表现分开呢?”,绝对的!!!ASP.NET Web Forms和ASP.NET MVCX都尝试帮助开发人员把应用逻辑和表现分离开来。它们之间的不同在于到底分离点在哪个位置?
ASP.NET Web Forms 把声明性标记从程序性逻辑分离出来。 ASPX结尾文件包含声明性标记,操控和程序逻辑驱动在后台代码类中进行。那样不错-它真的在一定程序上分离了关注点。它的限制是在实践中,大约一半的后台代码隐藏类是和UI控件的控制相关联的,另外一半操作是与应用的域模型相关联。表现的关注点和应用关注点都聚焦到这个代码隐藏类上。
MVC框架之所以存在是因为我们前面课程中了解到传统的Web Forms和因为MVC驱动的web应用平台的益处吸引和已经在现在世界中得到了应用论证。它识别出了表现经常混入一些逻辑,所以最有价值的分割点是在应用逻辑和表现逻辑之间。控制器和域模型类掌控应用和域逻辑,视图掌管表现逻辑。只要表现逻辑保持的非常简单,它就会很清楚的放入到ASPX文件中。
开发人员使用ASP.NET MVC或别的基于MVC的web应用平台已经发现这是构建它们应用的非常有效的方式。在视图中使用if或者foreach是没有任何错误的。表现有时候不得不这样做。毕竟,只要保持它的简单性你就会得到很干净的应用。
理解MVC视图具体是怎样工作的
ASPX文件是如何编译的
每次你创建一个新的视图页,Visual Studio给你一个人ASPX页面。它是一个HTML模板,但是它包含内嵌代码和服务器控件。当你在你的服务器端调用一个Web Forms或者MVC应用。你将会调用一系列这些未经编译的ASPX或ASCX文件。尽管这样,当ASP.NET 在运行时需要每个文件时,它会使用特定的内置页编译来把这些文件翻译为一个真正的.NET类。
ASPX文件只是直接以<%@ Page%>开始。它指出,至少,你的ASPX页应该来自哪里,而且几乎总是指定的,你的为内嵌代码使用的.NET语言。
例如:
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
看一下视图页生成了什么样的代码
视编译为:
晦涩的细节:ASP.NET MVC 的本地编译器不明白Inherits指令中的C#格式的语法。这使你可能会发生疑问使用例如 Inherits=”System.Web.Mvc.ViewPage<MyModel>”的声明怎么能创建强类型的视图?如果你看一下 /Views/Web.config,会发现一个叫做System.Web.Mvc.ViewTypeParserFilter的页面分析过滤器引用。这就是视图可以支持Inherites 指令中C#格式的语法的原因。不要去删除/Views/Web.config,否则,你将不能创建强类型的类型,尝试去解决它会让你发疯。
我已经简化了反编译列表,但它仍然是一个准确的描述。关键要注意的是每一个HTML片段--换行符和其它的--变成了一个对 HtmlTextWriter.Write()的调用,你的内联代码只是简单的没改变的传递到__Render()方法中,所以它变成了呈现进程的一部分。服务器控件,像示例中的ImageServerControl,被解析出来并被当做编译成员变量,通过调用它们的RenderControl()方法在适当的点上面插入。
现在你不会对你的内联代码和服务器控件在运行时怎么调用产生不确定性了。
注意:当你在Visual Studio使用生成->生成解决方案时,你的方案被编译,并返回编译是否正确,然而,这个编译过程不包括ASPX和ASCX文件,因为它们在运行时编译。如果你想要你的视图在常规编译过程编译。你可以使用一个叫做<MvcBuildViews>的项目设置。以后讲。
代码隐藏模型
如果你有使用ASP.NET Web Forms的经历,那么你对代码隐藏类就不会漠生,它的思想就是真接从System.Web.UI.Page继承代替生成页面。你可以建立一个中间基类(本身是从System.Web.UI.Page中派生的)并用它来承载其它代码,这将影响到该页面的行为。代码隐藏模型是为ASP.NET Web Forms设计的,它是Web Forms工作的主要方式:你使用代码隐藏类去承载定义在ASPX页面中服务器控件定义的事件。从技术上来讲,也可以使用Visual Studio来创建一个含有代码隐藏类的MVC视图,然后代码继承自Sytem.Web.Mvc.ViewPage或 System.Web.Mvc.ViewPage<Your Model Type>。
然而,代码隐藏类是ASP.NET MVC基本上是没有必要的,因为在MVC的关注点分离思想下,视图应该保持简单,所以很少使用代码隐藏处理事件。
理解ViewData
你知道在ASP.NET MVC 中,控制器通过一个叫做ViewData的对象为视图提供数据,它是一个ViewDataDictionary类型。这个类型提供了两种方法去提交数据。
利用语义字典:每一个ViewDataDictionary是一个可输入任意键值对的字典,如(ViewData[“data”] = DateTime.Now)。每对的名字是一个字符串,每一个值是一个对象。
使用一个称为模型的属性:每一个ViewDataDictionary有一个叫做Model的特殊属性,它可以以持有任意对象。例如,ViewData.Model = myPerson。在你的视图里,你可以使用简化的表示ViewData.Model
第一个方法的价值是显而易见的,你可以传递任意数据集。第二个方法依赖于你的视图类型继承的类型。ASP.NET MVC给你两种视图页基类选择。
如果你的视图继承自ViewPage,你创建了一个松散类型视图。在这种情况下,ViewData.Model是一种非特异性类型对象,这非常的有用。如果你打算使用ViewData做为字典模型,松散类型的视图是特别合适的。
如果你的视图继承自ViewPage<T>,你已经创建了一个强类型视图,在这种情况下,ViewData.Model是T类型,你很容易从智能感知中提取数据。
实际上,如果你的视图页主要呈现一些域模型对象,你可以使用ViewData<T>,而如果你是呈现Person类的集合,你应该使用Viewpage<IEnumerable<Person>>.
输入控件
添加任意标记属性
<%=Html.TextBox(“myText”, “val”, new{someAttribute=”someval”})%>
将会呈现
<input id=”mytext” name=”mytext” someAttribute=”someval” value=”val” />
提示:C#编译器不希望你使用C#保留字作为属性名字。所以,如果你想通过new{class=”myClass”}来呈现一个class属性,你会得到一个编译错误。为了防止这样,你需要在C#的保留字前面加上@如new{@class=”myClass”}。
呈现链接和URL
呈现下拉菜单和多选列表
在Microsoft.Web.Mvc.dll中额外赠送的帮助方法
ASP.NET MVC的未来组合中,Microsoft.Web.Mvc.dll,包含了一定数量的HTML辅助方法,它们还未加入到MVC Framework的核心中,可能是因为被考虑到不怎么重要或许还没有打磨好,但是在一些情况下还是很有用的。你可以从www.codeplex.com/aspnet下载该组件。确保你下载的是面向ASP.NET MVC 2。在你使用这些辅助之前,你需要添加这个引用,并且要修改Web.config文件,这样就可以把命名空间导入到你的每一个视图里。
做完了这些,你就可以使用下表中的辅助了
其它的HTMl辅助
呈现Form标记
框架也提供了呈现<form>标签,名字为Html.BeginHtml()和Html.EndForm()。使用这个的好处就是它会根据你的路由配置和目标控制器和动作产生合适的动作属性,这些辅助和你前面看到的基本一样。
有两种使用方法
<% Html.BeginForm("MyAction", "MyController"); %>
... form elements go here ...
<% Html.EndForm(); %>
或者下一种
<% using(Html.BeginForm("MyAction", "MyController")) { %>
... form elements go here ...
<% } %>
这两种代码片段生成完全相同的输出。所以你可以使用它们中的任一个。根据默认的路由配置,它们将会输出下面
<form action="/MyController/MyAction" method="post">
... form elements go here ...
</form>
如果你要为表单的动作URL指定别的路由参数,你可以这样
<% Html.BeginForm("MyAction", "MyController", new { param = "val" }); %>
将会呈现为
<form action="/MyController/MyAction?param=val" method="post">
表单向同一个URL返回数据
你可以省略控制器和动作名,这样辅助将会产生一个向现在的请求URL的form
<% using(Html.BeginForm()) { %>
... form elements go here ...
<% } %>
生成
<form action="current request URL" method="post" >
... form elements go here ...
</form>
ASP.NET MVC开发者经常在接收同一个URL的GET和POST两种请求时用Html.BeginForm()重载。最具代表性的,一个GET请求显示一个原始的表单,然后一个POST请求控制表单验证和别的表单显示(如是否通过验证啦什么的)或者引导用户跳转的别一个动作(如果验证动过且保存),例如:
public class SomeController : Controller
{
public ViewResult MyAction() { /* Displays the form */ }
[HttpPost]
public ActionResult MyAction(MyModel incomingData) { /* Handles the POST */ }
}
创建你自己的HTML辅助方法
内建的辅助方法没有什么神圣玄幻,它只不是返回MvcHtmlStrings类型的.NET方法,所以你可以在你的应用中自由的添加新的辅助方法。
举例:在线电影和我HTML5变得越来越广泛,所以现在我们为呈现HTML5的<video>标签创建一个新的辅助方法。创建一个叫做 VideoTagExtensions的静态类(例如 在 /Views/Helpers/VideoTagExtensions.cs):
public static class VideoTagExtensions
{
public static MvcHtmlString Video(this HtmlHelper html, string src)
{
string url = UrlHelper.GenerateContentUrl(src, html.ViewContext.HttpContext);
TagBuilder tag = new TagBuilder("video");
tag.InnerHtml = "Your browser doesn't support video tags.";
tag.MergeAttribute("src", url);
tag.MergeAttribute("controls", "controls"); // Show Play/Pause buttons
return MvcHtmlString.Create(tag.ToString());
}
}
<%: MyApp.Views.Helpers.VideoTagExtensions.Video(Html, "~/Content/myvideo.mp4") %>
生成
<video controls="controls" src="/Content/myvideo.mp4">
Your browser doesn't support video tags.
</video>
Your browser doesn’t support video tags只是在不支持HTML5的浏览器里显示。
在你需要的视图页头部写入下面
<%@ Import Namespace=”MyApp.Views.Helpers” %>
或在web.config中写入
<add namespace=”MyApp.Views.Helpers” />
可以在视图中直接写入<%=Html.Video(“~/Content/myvideo.mp4”) %>
使用局部视图
你经常会重使用分散在不同地方的代码段,不要复制,粘贴---提取出它们放入到一个局部视图中。局部视图和自定义HTMl辅助方法相类似,不同的地方在于局部视图需要选择ASCX。
在这一部分,你将会学到在默认的Web Forms视图引擎中如何创建局部视图,和使用不同的方法向它们绑定数据的方法。首先,看一下局部视图和常规视图的一致性。
- 就像视图页是一个Web Forms页一样, 一个局部视图是一个Web Forms用户控件。
- 一个视图被编译为一个从ViewPage派生的类(继承于Page,所有Web Form页面的基类)。一个局部视图在编译为一个从ViewUserControl派生的类(继承于UserControl,所有Web Forms 用户控件的基类。中间基类都为MVC添加了特定的概念,如ViewData, TempData,还有HTML辅助方法(HTml.*, Url.*, etc.)。
- 你可以创建一个强类型的视图页,通过继承ViewPage<T>。相同的,你也可以通过继承 ViewUserControl<T>使一个局部视图变成强类型国。在这两种情况下,它使用一般同等类型代替了ViewData, Html, 和Ajax属性。它使用Model属性变为类型T。
创建和呈现一个局部视图
你可以通过在/Views文件夹下右击并且选择Add>View创建一个新的局部视图,在弹出的添加对话框中,选中”Create a partial view(.ascx)” 。MVC希望你把视图文件放入到文件夹/Views/nameofController或/Views/Shared(或者如果你使用了区域,也可以放入 /Areas/nameOfArea/)
在/Views/Shared文件夹下创建一个叫做MyPartial的局部视图,并添入下列HTML标记
<% Control Language=”C#” Inherits=”System.Web.Mvc.ViewUserControl” %>
<i>Hello from the partial view</i>
为了呈现这个局部视图,去你的应用目录下的任一视图页添加如下的代码
<h2><%= Html.Encode(ViewData["Message"]) %></h2>
<p><%=Html.Partial("MyPartial") %></p>
向响应流直接呈现一个局部视图
当Html.Partial()调用你的局部视图,它会在内存里创建一个StringWriter,告诉局部视图向StringWriter散发它的输出(然后在内存里收集结果集),然后返回一个MvcHtmlString作为最终的StringWriter内容。这意味着你可以像使用别的辅助方法那样使用它。
如果你想直接在响应流中输出局部视图,绕过任何内存中的StringWriter。你可以使用Html.RenderPartial()。
<% Html.RenderPartial(“MyPartial”); ?>
注意它没有等号,并且它没有返回任何结果,它只是真接写入到了响应流中。
它们的性能通常是没有意义的,但是有的时候是个例外,例如你的局部视图返回了一些特别大的文本或者在一个页面中呈现数百个局部视图。
向一个局部视图中传递ViewData
正如向你期望一个视图一样,局部视图也有一个ViewData属性。但是默认情况下,它只是一个视图ViewData对象的直接引用,这就意味着局部视图拥用完全相同的数据。不管是字典内容或它的ViewData.Model对象。
为一个局部视图传递一个详细的模型对象
当你调用Html.Partial()或者Html.RenderPartial()时,你可以提供一个值作为第二个参,叫做model,它将会变成局部模型对象。通常情况下,你需要使用这个重载当呈现一个强类型的局部视图时。
举例,如果你的控制器为ViewData传递一个Person对象
Public class Person
{
Publis string Name{get;set;}
Public int Age{get;set;}
}
Public class HomeController : Controller
{
ViewData["someperson"] = new Person { Name="Allan", Age=24};
return View();
}
MyPartial.ascx
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcTest.Controllers.Person>" %>
<i>Hello from partial view</i>
<%=Model.Name %> is <%=Model.Age %> years old
在 Index.aspx中
<%=Html.Partial("MyPartial", ViewData["someperson"]) %>
就像你看到的这样,我们为Html.Partial()传递的参数变成了局部视图的Model对象。记住在视图中,Model只是ViewData.Model的快捷方式,ViewData是一个包含一系列字典实体,还有特殊的ViewData.Model值的数据结构。
向局部视图传递一个可循环项集合
在动作方法中准备一个IEnumerable<Person>,然后呈现一个从ViewPage<IEnumerable<Person>>继承的强类型视图。
IEnumerable<Person> viewModel = new List<Person> {
new Person{ Name="Archimedes", Age=8},
new Person{Name = "Aristotle", Age=23},
new Person{Name="Allan", Age=75}
};
ViewData["someperson"] = viewModel;
视图
<%=Html.Partial("MyPartial", ViewData["someperson"]) %>
局部视图
<% foreach(var person in Model) {%>
<%=person.Name %> is <%=person.Age %> years old<br />
<%} %>
每一个ASP.NET MVC开发人员都喜欢使用简单的foreach语名来依次得到数据,而不是使用在ASP.NET Web Forms中流行的数据绑定机制。Foreach简易,不需要特定的OnDataBound()事件,还提供了智能提示。但是如果你只是喜欢恐怖的原始代码,你仍然可以使用Web Forms。