巨大转变!ASP.NET MVC2用户界面新实践
本篇中,我们将探讨ASP.NET MVC 2.0框架所支持的用户界面编程问题。在上一篇 中,我们已经对此有了简单的了解,接下来我们还会详细讨论。首先,我们来看一下如何构建用户接口的问题。
无论是在ASP.NET Web表单还是在MVC框架中,用户界面都要经历一个呈现的过程。在Web表单中,Page类使用一个呈现进程实现把控件层次翻译成浏览器端的HTML内容。隐藏代码文件负责提供影响这些控件状态的属性及方法,最终把每一个控件转换成浏览器端对应物。当然,页面生命周期也会影响这一过程。
ASP.NET MVC则从根本上改变了上述过程。MVC不再是操作一个控件层次结构,而是采用自顶向下的模式进行直接呈现,这很类似于早期的ASP工作模式。然而,ASP.NET MVC和早期ASP之间的核心区别在于,MVC的业务逻辑是与视图层是分离的,而不是缠绕在一起的。每个视图都可以有自己对应的模型,可以直接访问“模型”(其中包含自控制器返回的数据)中的属性,从而实现直接引用特定于视图的数据。在这篇文章后面的示例中,你会看到我们将频繁地使用这一过程。
对大多数开发人员而言,构建用户界面的方法可能是一个巨大的转变。例如,在Web表单中,如果你想通过编程方式在HEAD元素中添加一个样式表,他可以通过把代码放到Load事件处理程序中实现这一目的。但在MVC框架下,只能在HEAD元素被呈现时把样式表注入到视图中。你不能直接把样式表引用为对象。
一、创建用户界面
MVC程序在用户界面构建方面与以前的Web表单仍然使用类似的结构,例如,它也使用@Page指令,而且设置参数是一致的(但是,隐藏代码引用指向一个标准的ASP.NET MVC类)。它仍然使用HTML标记与服务器端代码混合模式。但是,为了使整个服务器端处理更加容易,MVC不再使用控件方式而是使用HtmlHelper类。以前是服务器端控件的功能现在变换为一个HtmlHelper类的方法。例如,为了在浏览器中呈现一个文本框元素,我们不必再使用TextBox控件,而是通过下面的代码来实现。
<%= Html.TextBox(“Control”).ToHtmlString()%>
上面这一句能够把一个<input type="text">元素直接呈现到响应流中。请注意这里的ToHtmlString方法调用。这是ASP.NET MVC 2中提供的一个新功能—不再是以前那样直接返回一个字符串。但是,如果你喜欢你可以省略这个方法调用,代码会像以前一样工作(即方法将被隐式调用)。
下面展示了一个使用这些助理类的用户界面示例:
清单1—在MVC框架下定义表单的代码示例
<div>
Name: <%= Html.TextBox("Name")%>
</div>
<div>
State: <%= Html.DropDownList("State", new SelectListItem[]
{
new SelectListItem { Text = "Pennsylvania", Value = "PA" },
new SelectListItem { Text = "Ohio", Value = "OH" },
new SelectListItem { Text = "Maryland", Value = "MD" }
})%>
</div>
<div>
<%= Html.ActionLink("View State Codes", "Index", "Home")%>
</div>
<div>
<%= Html.CheckBox("SecurityAgreement", true)%>
Reviewed security agreement
</div>
上述每一个方法都返回一个描述HTML控件的字符串。使用这些方法是考虑到MVC的内置工作特征,因为MVC可以使用某些模式把这些特征连接起来。如果你喜欢的话,你当然可以直接定义这些控件的HTML代码。
二、新的“For”后缀助理方法
MVC 2中又提供了一组新的带有For后缀的助理方法(这实质上都是一些扩展方式,你可以通过在这些方法上单击右键并转到对应定义的方式来跟踪观察),诸如TextBoxFor,CheckBoxFor等等。这些方法通过一个lambda表达式来选择模型内的属性并最终用于显示到用户界面中。例如,Html.TextBoxFor(i=>i.FirstName)语句就会提取模型中第一个姓名的值。
清单2—使用带有For后缀的助理方法(扩展方法)的代码示例
<%= Html.HiddenFor(i => i.ProjectStoryScenarioKey)%>
<%= Html.HiddenFor(i => i.ProjectStoryKey)%>
<div>
<div>
<%= Html.TextBoxFor(i => i.ScenarioNumber)%>
<%= Html.ValidationMessageFor(i => i.ScenarioNumber, "*")%>
</div>
<div>
<%= Html.TextAreaFor(i => i.Description)%>
<%= Html.ValidationMessageFor(i => i.Description, "*")%>
</div>
</div>
归纳来看,上面的代码做了两件事情:第一是使用模型中提供的内在值呈现基本的HTML元素;第二,元素的ID和name都使用了lambda表达式中提供的文本(在这个例子中是ScenarioNumber和Description)。借助于这些方法,我们可以很方便地呈现直接对应于模型部分的内容,并且有助于控制模型绑定(稍后进行解释)。
三、数据注解
数据注解有利于在界面中呈现数据时从数据模型中提取特定的内容。其实,远不止这些。通过使用lambda表达式,ASP.NET MVC框架还会搜索任何添加于属性上的数据注解。数据注解其实是一个你在模型上指定的属性。这些注解可以控制辅助方法如何以不同的方式工作。例如,让我们来分析一下下面的示例属性:
清单3—数据注解的代码示例
[
DisplayName("Boolean Val"),
Required
]
public bool BoolValue { get; set; }
[
DisplayName("Boolean Val"),
Required
]
public bool BoolValue { get; set; }
在这里,我们的模型类中定义了一个属性BoolValue,其上添加了两个注解,后者用于进行验证(以后的文章中会专门讨论这个问题),前面那个注解用于存储字段的显示名称。你会注意到,如果你想使用接下来的扩展方法输出一个相应于这个属性的标签元素的话,那么使用上面的DisplayName属性是非常方便的。
清单4—呈现标签的代码示例
<div><% // Renders Boolean Val %>
<%= Html.LabelFor(i => i.BoolValue)%>
<%= Html.CheckBoxFor(i => i.BoolValue)%>
</div>
上面的代码使用“Boolean Val”名字生成一个标记元素,接着生成一个名称为模型值中对应名字的复选框元素。
四、模型绑定综合举例
现在,我们通过一个较完整的例子来分析模型绑定的整个工作过程。一开始,我们必须先有一个指向我们的视图的控制器。下面的示例中提供的第一个行为方法通过引用一个SampleForm/ForIndex网址返回视图。第二个行为方法则简单地响应一个表单回寄。
清单5—定义控制器的代码示例
public class SampleFormController : Controller
{
[HttpGet]
public ActionResult ForIndex()
{
return View(new SampleFormTestClass
{
AltBoolValue = "No",
BoolValue = true,
ComboValue = "PA",
DateValue = DateTime.Now,
DecimalValue = 3.4M,
IntValue = 23,
StringValue = "A Test"
});
}
[HttpPost]
public ActionResult ForIndex(SampleFormTestClass test)
{
return View();
}
}
上面定义的这个控制器非常简单。只是,请特别注意一下其中提供的初始值。相应于ForIndex视图所使用的模型是SampleFormTestClass,其中包含了一些数据注解。MVC框架中针对验证支持定义了大量的注解。但是篇幅所限,本文不再列举。接下来给出的是模型部分的定义。
清单6—定义模型的代码示例
public class SampleFormTestClass
{
[DisplayName("Alternative Boolean Val (Yes/No)")]
public string AltBoolValue { get; set; }
[
DisplayName("Boolean Val"),
Required
]
public bool BoolValue { get; set; }
public string ComboValue { get; set; }
[DataType(DataType.DateTime)]
public DateTime DateValue { get; set; }
public decimal DecimalValue { get; set; }
public int IntValue { get; set; }
public string StringValue { get; set; }
}
在下面的表单定义中,用户界面部分正是使用了上面模型中定义的属性。在此我想逐段进行介绍。第一部分是表单的定义。你可以使用下面的语法之一来指定一个表单。
清单7—定义表单的内联代码示例
<% Html.BeginForm(); %>
Form Content
<% Html.EndForm(); %>
<% using (Html.EndForm()){%>
<%}%>
在表单标签内的所有内容都被回调给服务器端带有[HttpPost]属性标记的ForIndex行为方法。首先呈现的是对应于BoolValue属性的标签和复选框控件。你应当还记得,它有一个DisplayName属性标记,所以在呈现文本时文本内容将是“Boolean Val”而不是实际的属性名字(默认情况下是这样的)。
请继续往下看。
清单8—构建单标签和复选按钮元素的内联代码示例
<div>
<%= Html.LabelFor(i => i.BoolValue)%>
<%= Html.CheckBoxFor(i => i.BoolValue)%>
</div>
另外,如果你喜欢使用单选按钮来表达yes/no字段,下面的代码给出了一种选择。虽然没有提供类似于RadioButtonList这样的控件,但是每一个单选按钮都可以映射到一个布尔型或者其他类型的属性(本例是一个字符串),并最终呈现为单选按钮列表。第二个参数并不是单选按钮的文本。实际上,它是与从列表中选择的项相匹配的值。因为模型中没有定义任何值,第二个单选按钮便被选中。
清单9—构建单选按钮元素的内联代码示例
<div>
<%= Html.RadioButtonFor(i => i.AltBoolValue, "Yes")%>
<%= Html.RadioButtonFor(i => i.AltBoolValue, "No")%>
</div>
在上面的代码中,我们讨论了如何把文本框和文本区呈现到屏幕上。其实,接下来还有一个隐藏的字段和一个密码框。对于这些控件,仅需注意的是,如果你有一个专门的数据类型(如日期时间型或一个整数)要呈现到一个文本框中,那么,MVC框架尚未提供针对这些类型数据值的任何隐含错误检查。为此,你可以在这些值上使用验证机制来防止上述错误。如果由于某种原因,输入了一个无效的值,那么该值将被省略,代之以默认值(例如默认日期为1/1/0001)。
清单10—构建文本元素的内联代码示例
<div>
<%= Html.TextBoxFor(i => i.DateValue)%>
</div>
<div>
<%= Html.TextAreaFor(i => i.StringValue)%>
</div>
<div>
<%= Html.HiddenFor(i => i.IntValue)%>
</div>
<div>
<%= Html.PasswordFor(i => i.StringValue)%>
</div>
MVC也支持标准列表类型。以下方法能够使用一个数据列表来呈现一个下拉列表和列表框元素(可以使用静态数据也可以使用数据绑定实现)。注意,其中是如何提取基本值作为默认选择项的。这可能是发生在控制器内而不是用户界面上的一个典型的操作,但为了说明问题,我特意把它们放到了用户界面部分。
清单11—构建列表的内联代码示例
<div>
<%= Html.DropDownListFor(i => i.ComboValue, new SelectListItem[]
{
new SelectListItem { Text = "Ohio", Value = "OH" },
new SelectListItem { Text = "Pennsylvania", Value = "PA" },
new SelectListItem { Text = "Maryland", Value = "MD" }
})%>
</div>
<div>
<%= Html.ListBoxFor(i => i.ComboValue, new SelectListItem[]
{
new SelectListItem { Text = "Ohio", Value = "OH" },
new SelectListItem { Text = "Pennsylvania", Value = "PA" },
new SelectListItem { Text = "Maryland", Value = "MD" }
})%>
</div>
值得注意的是,并不是每一个HTML元素都有一个关联的HTML辅助方法。举例来说,任何类型的按钮元素就没有相应的ButtonFor方法。而且,也没有为超链接按钮提供相应的支持。这是一个重大的改变,这也是由于MVC框架工作方式与Web表单框架根本形式不同所决定的。
最后的界面如图1所示。
▲
图1—定义用户界面中的列表元素示例
上面示例中,我特意地交换了AltBoolValue和BoolValue UI控件的原始值,改变了组合框的选择,并创造一个无效的日期,从而导致下面的值被调回。
▲
图2—被回寄的值
这里给出了模型绑定过程最大的特点。通过使用我们的lambda表达式,表单中回寄的值被自动映射到类的属性上,而该类对应于行为方法的参数。因此,既然字段回寄与我们的模型类相关联,所以,MVC会自动把这些值提供给我们的对象,而不必由我们手动执行此任务。如果你喜欢,你可以创建一个定制的模型绑定器。但是,有关这个内容已经超出了我们的主题了。
如果你不喜欢以这种方式直接使用对象引用,而且想更多地控制上述过程,另一种方法是指定一个FormCollection集合作为行为方法参数,其中可以包含所有被回寄的表单值。这样,您就可以更多地控制其中的数据。你还可以使用UpdateModel或TryUpdateModel方法把这个表单集合应用到一个模型对象上。
五、小结
正如你所见,ASP.NET MVC框架提供了强大的功能。一旦你理解了这篇文章中提到的构建模式,你就可以开始创建复杂的用户界面。关键是理解MVC怎样控制整个生命周期/API/层次结构,并使用内联呈现方式,以及理解MVC所利用的逻辑分离原理。
在本文中,我们研究了使用一系列HTML辅助方法来简化用户界面的构造。MVC框架能够使你取得某种形式的值并直接应用于相应的对象上。深入理解这些基础知识是你将来构建复杂用户界面的必需,因为复杂的UI建设需要预先深思熟虑的规划。我们将在以后的文章中研究这一点。
【相关文章】:巨大转变!教你使用ASP.NET MVC2新功能