2.4 创建一个简单的数据录入应用程序
本章的其余部分将通过一个简单的数据录入应用程序来考查 MVC 的更多基本特性。本小节打算分步进行,目的是演示 MVC 的运用。
B1、设计一个数据模型
在 MVC 中,M 代表模型(Model),它是应用程序最重要的部分。
模型是定义应用程序主题的现实对象、过程以及规则的表示,称为域。
模型,通常称为域模型,包含应用程序域中要建立的 C#对象,称为域对象。这些域对象构成了应用程序的全部事物以及操纵这些
对象的方法。
视图和控制器以一致的方式将域暴露给客户端。
一个设计良好的 MVC 应用程序,必须从设计良好的模型开始,它是随后添加控制器和视图的焦点。
(对于 “晚会邀请”应用程序,不需要复杂的模型。因为这是一个简单的应用程序,只需要创建一个域类(可以称为 GuestResponse),由它负责存储、验证并确认“回复邀请”)
添加模型类
MVC 的约定是将建立模型的类放在 “Models”文件夹中,该文件夹是 Visual Studio 最初建立项目时创建的。
1、右击 “Models”文件夹——“添加”——“类”
2、将文件名设置为 “GuestResponse.cs”
3、单击“添加”按钮。(创建完成后修改文件内容——为模型对象类添加属性)
(可以通过问号“?” 将 “int”、“bool” 等类型的属性设置为可空的类型)
B2、★ 链接动作方法
该应用程序的目标之一是要包括一个回复邀请的表单,因此需要在 Index.cshtml 视图中添加一个指向它(动作方法)的链接。
……
@Html.ActionLink("现在回复", "RsvpForm")
……
Html.ActionLink 是一个 HTML 的辅助器方法(Helper Method)
MVC 框架附带了一组内置的辅助器方法,它们可以方便地用来渲染 HTML 的链接、文本输入框、复选框 以及其他种类的内容。
这个 ActionLink 方法有两个参数:第一个是该链接的显示文本,第二个是用户单击该链接时将要执行的动作。
(第21~23章将解释完整的 HTML 辅助器方法集合)
Html.ActionLink 方法已经检测了应用程序的 URL 路由配置,并得出 /Home/RsvpForm 是一个指向 HomeController 控制器
上 RsvpForm 动作的 URL。
(与传统的 ASP.NET 应用程序不同,MVC 的 URL 并不对应物理文件。每个动作方法有它自己的 URL,而 MVC 使用 ASP.NET
的路由系统将这些 URL 转换成动作)
创建动作方法:
此时,可以在 HomeController 类中添加一个名称为 “RsvpForm” 的方法来完成这一工作。
public ViewResult RsvpForm()
{
return View();
}
添加强类型视图:
这里打算为 RsvpForm 动作方法添加一个视图,但采取了稍有不同的方式——创建一个强类型视图。
强类型视图意在渲染一个特定的域类型,而且,如果指定了想使用的类型(本例是 GuestResponse),MVC 将能够提供一些
便于使用这个类型的便捷方法,从而可以在视图中方便地使用这个类型对象。
(在做进一步工作之前,要确保已编译了 MVC 项目。如果已经创建了 GuestResponse 类,但未进行编译,MVC 将不能为这个
类型创建强类型视图。 要编译应用程序,可以 “生成”——“生成解决方案”,或简单地按快捷键 F6)
1、在代码编辑器中右击 ResvForm 方法,然后选择 “添加视图”
2、确保将 “视图名”设置为 “ResvForm”,将“模板”设置为 “空模板”,并从“类模板”字段的下拉列表中
选择“GuestResponse(PartyInvites.Models)”,让(视图)选项中的复选框处于未选状态。
3、单击 “添加”按钮。(这是另一种结构的 HTML 文件,它包含了一个 @model 的 Razor 表达式。过一会儿就会明白,这是
强类型视图,并是为视图提供便利的关键——@model PartyInvites.Models.GuestResponse)
(在创建视图时,所选择和勾选的选项决定了视图文件的初始内容(跟别的文件没有关系),其他没什么作用。例如,
你可以将常规视图修改成强类型视图,只需在代码编辑器中添加或去除 @model 指示符 即可)
B3、建立表单
现在已经创建了强类型视图,可以扩建这个 RsvpForm.cshtml 的内容(在 RsvpForm.cshtml 文件中创建表单视图)
@using (Html.BeginForm())
{
<p>姓名:@Html.TextBoxFor(x=>x.Name) </p>
<p>邮箱:@Html.TextBoxFor(x=>x.Email) </p>
<p>手机:@Html.TextBoxFor(x=>x.Phone) </p>
<p>是否参加?@Html.DropDownListFor(x=>x.WillAttend, new[ ] {
new SelectListItem() { Text="是,我参加", value=bool.TrueString },
new SelectListItem() { Text="否,不参加", value=bool.FalseString }
}, “请选择”)
</p>
<input type="submit" value="回复邀请" />
}
这里对 GuestResponse 模型类的每一个属性都使用了一个 HTML 辅助器方法,以便渲染一个适当的 HTML 的 input 控件。
这些辅助器方法都能够用一个 lambda 表达式来选择与 input 元素有关的属性。(如:@Html.TextBoxFor(x=>x.Phone))
这个 TextBoxFor 辅助器方法会生成一个 input 元素的 HTML,将该元素的 type 参数设置为“text”,id 和 name 标签属性设置
为“Phone”,Phone 是所选域类的属性名。于是为模型属性 Phone 生成了一个文本输入框的 HTML 标记,如下所示:
<input id="Phone" name="Phone" type="text" value="" />
这种灵活的特性是能够起作用的,因为 RsvpForm 视图是强类型的,而且已经告诉 MVC,GuestResponse 是希望用该视图
渲染的类型。——这为 HTML 辅助器方法提供了所需的信息,使其能够理解从 @mldel 表达式所读取的属性是哪一种数据类型。
替代运用 lambda 表达式的另一种方法是将模型类型的属性名指定为一个字符串,如:@Html.TextBox("Email")
另一个便利的辅助器方法是 Html.BeginForm,它生成一个回递给动作方法的 HTML 表单元素(<form>元素)
这里未给辅助器方法传递任何参数,于是它假设要回递的目标是请求此 HTML 文档的同一个 URL。
一个整洁的技巧是将它封装在一个 C# 的 using 语句中,如:@using (Html.BeginForm()) { }。
正常情况下,像这样运用 using 语句,会在对象超出范围时确保收回对象所占用的资源。例如,它通常用于数据库连接。
但这里并不是清理对象,而是 Html.BeginForm 辅助器在它超出范围时关闭 HTML 的 form 元素。——这意味着,
Html.BeginForm 辅助器方法会创建 form 元素的两部分(<form>元素的开标签和闭标签),如下所示:
<form action="/Home/RsvpForm" method="post">
…这里是表单内容…
</form>
这里的关键是演示如何用 HTML 辅助器方法创建一个表单。
B4、处理表单
这里尚未告诉 MVC,将表单递交给服务器时要做什么。(此时,单击“恢复邀请”按钮只会清除掉表单中已经输入的值。这是因为
该表单会回递给 Home 控制器中的 RsvpForm 动作方法,这只是告诉 MVC 再次渲染该视图——视图被再次渲染时,输入的数据会消失)
为了接收并处理表单所递交的数据,这里打算使用一个聪明的特性——添加第二个 RsvpForm 动作方法,以形成如下作用:
一种方法用于响应 HTTP 的 GET 请求:GET 请求是某人单击一个链接时,浏览器正常发出的请求。当有人第一次访问/Home
/RsvpForm 时,这个动作负责显示最初的空白表单。
一种方法用于响应 HTTP 的 POST 请求:默认情况下,用 Html.BeginForm() 渲染的表单是由浏览器作为一个 POST 请求
递交的。这个动作负责接收所递交的数据,并决定用它做什么。
以独立的 C#方法分别处理 GET 和 POST 请求,有助于保持控制器代码整洁,因为这两种方法有不同的职责。
这两种方法都由相同的 URL 进行调用,但 MVC 确保会根据所处理的是 GET 请求或 POST 请求,来调用合适的方法。
在 HomeController.cs 文件中添加一个支持 POST 请求的动作方法:
……
using PartyInvites.Models;
……
[ HttpGet ]
public ViewResult RsvpForm() { return View(); }
[ HttpPost ]
public ViewResult RsvpForm(GuestResponse gr)
{
return View("Thanks", gr);
}
这里在已有的 RsvpForm 动作方法上添加了 HttpGet 注解属性。这是告诉 MVC,该方法应该仅用于 GET 请求。
然后添加了一个重载版的 RsvpForm 方法,它带有一个 GuestResponse 参数,并运用了 HttpPost 注解属性。(该注解属性告诉 MVC ,这个新方法将处理 POST 请求)
这里已经引入了 PartyInvites.Models 命名空间,这样便可以直接使用 GuestResponse 模型类型,而不需要使用这个类的限定名。
B5、添加验证
验证是为了防止用户输入无意义的数据,甚至是递交空白的表单。
在 MVC 应用程序中,验证典型地运用于域模型,而不是用户界面。这意味着可以在一个地方(模型类中)定义验证条件,而在运用模型类的任何地方生效。
ASP.NET MVC 支持验证规则声明,这是以 System.ComponentModel.DataAnnotations 命名空间中的注解属性进行定义的 —— 意即,验证约束是使用标准的 C# 注解属性特性来表示的。
如下演示如何将这些注解属性运用于 GuestResponse 模型类:
using System.ComponentModel.DataAnnotation;
[ Required( ErrorMessage = "Please enter your name" )] // 意思是运用 Name 属性的地方不能为空,为空则显示对应的错误信息
public string Name { get; set; }
[ Required( ErrorMessage = "Please enter your email address" )] //一个属性可以有多个注解属性
[ RegularExpression( ".+\@.+\..+", ErrorMessage = "Please enter a valid email address" ) ]
public string Email { get; set; }
MVC 会自动地侦测这些验证注解属性,并在模型绑定过程中将它们用于验证数据。
一个可空的 bool 型(bool?)有3个可能的值:true、false 和 null。 如果用户尚未选择,系统会默认用 null 来表示,这会让 Required 注解属性能够报告一个验证错误。这是 MVC 框架如何优雅地将 C# 特性与 HTML 和 HTTP 相融合的一个很好的示例。
可以在控制器中使用 ModelState.IsValid 属性来检查是否有验证问题。(如下所示,在 HOME 控制器类的启用 POST 的 RsvpForm 动作方法中的实现)
[ HttpPost ]
public ViewResult RsvpForm( GuestResponse guestResponse)
{
if( ModelState.IsValid)
{
return View("Thanks", guestResponse) ; // 第一个参数是视图名
}
else
return View() ;
}
如果没有验证错误,便让 MVC 渲染 Thanks 视图。 如果有验证错误,则通过调用不带参数的 View 方法重新渲染 RsvpForm 视图。
在有错误时,仅显示表单并不十分有用,还需要给用户提供一些指示,告诉他们有什么问题,以及为什么不能接受他们所递交的表单。其实现办法是在 RsvpForm 视图中使用 Html.ValidationSummary(验证摘要)辅助器方法。
注意,在表单中输入的数据是被保留的,而且在带有验证摘要的视图被重新渲染时,这些数据会被再次显示出来。这是模型绑定特性的另一个好处,它简化了表单数据的工作。
高亮显示无效字段:
当模型类的某个属性验证失败时(前提是对应的属性运用了 Required 注解属性),HTML 辅助器方法会生成稍有不同的 HTML(在 input 标签内产生了一个值为 "input-validation-error" 的 class 标签属性)——通过创建样式表便可以利用这一特性,该样式表可以为这个 class 以及其他 HTML 辅助器方法所生成的 class 设置一些不同的 CSS 样式(这样就可以对不同的 class 值设置一些不同的显示效果)
MVC 项目的约定是:将静态内容(如 CSS 样式表等)放在名称为 Content 的文件夹中(可能要自己新建)