本节书摘来自异步社区《精通 ASP.NET MVC 5》一书中的第2章,第2.4节,作者: 【美】Adam Freeman(弗瑞曼 A.),译者: 张成彬 , 徐燕萍 , 李萍 , 林逸 责编: 张涛, 更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.4 创建一个简单的数据录入应用程序
本章的其余部分将通过建立一个简单的数据录入应用程序来考查MVC的更多基本特性。本小节打算分步进行,目的是演示MVC的运用,因此会跳过对幕后工作原理的一些解释。但不必担心,在后面的章节中会重新深入地讨论这些论题。
2.4.1 设置场景
假设一个朋友决定举行一个“新年除夕晚会”,于是她请笔者为其创建一个Web应用程序,以便让受邀人进行电子回复(RSVP)
。她的要求有以下4个关键特性。
一个显示此晚会信息的首页。
一个可以用来进行电子回复(RSVP)
的表单。
对RSVP
表单的验证,它将显示一个“感谢你”的页面。
当完成RSVP
时,给晚会的主人发送一份电子邮件。
以下小节将增强本章开始已经创建的MVC项目,并实现上述这些特性。利用前面已经涉及的内容,并对现在的视图添加一些HTML
,显示出晚会的细节,便可以完成上述列表中的第一项。清单2-8显示了对Views/Home/Index.cshtml
文件所添加的内容。
清单2-8 在Index.cshtml
文件中显示晚会的细节
开发已经开始。如果运行该应用程序,会看到晚会的详情——没错,这只是一个详情占位符,但能从中获得思路(指这个页面只是一些简单的消息,在实际应用中你可以用丰富的内容来替换这里的消息,故将其说成是占位符),如图2-14所示。
2.4.2 设计一个数据模型
在MVC中,M代表模型(Model),它是应用程序最重要的部分。模型是定义应用程序主题的现实对象、过程以及规则的表示,称为域(Domain)。模型,通常称为域模型(Domain Model),包含应用程序域中要建立的C#对象,称为域对象(Domain Object)。这些域对象构成了应用程序的全部事物以及操纵这些对象的方法。视图和控制器以一致的方式将域暴露给客户端。一个设计良好的MVC应用程序,必须从设计良好的模型开始,它是随后添加控制器和视图的焦点(现在国内流行将这里的“域”称为“领域”,译者认为这种术语不妥,但如果读者不习惯这里的“域”术语,可以将本书的“域”理解为“领域”)。
对于PartyInvites
(晚会邀请)应用程序,不需要复杂的模型。因为这是一个简单的应用程序,只需要创建一个域类(可以称为GuestResponse
),由它负责存储、验证并确认RSVP
。
添加模型类
MVC的约定是将建立模型的类放在“Models”
文件夹中,该文件夹是Visual Studio最初建立项目时创建的。右击“Solution Explorer
(解决方案资源管理器)”窗口中的“Models”
,并从弹出的菜单中选择“Add(添加)”→“Class(类)”
,将文件名设置为“GuestResponse.cs”
,单击“Add(添加)”按钮,创建这个类。编辑该类的内容,使其与清单2-9吻合。
提示:
如果没有“Class(类)”
菜单项,可能是仍在运行Visual Studio的调试器。因为调试器正在运行应用程序期间,Visual Studio会限制对项目进行修改。
清单2-9 GuestResponse.cs
文件中定义的GuestResponse
域类
namespace PartyInvites.Models {
public class GuestResponse {
public string Name { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public bool? WillAttend { get; set; }
}
}
提示:
你可能已经注意到,WillAttend
属性是一个可空的(Nullable
)bool
型,这意味着它的取值可以是true、false或null。本章稍后的“添加验证”小节将解释其基本原理。
2.4.3 链接动作方法
该应用程序的目标之一是要包括一个RSVP的表单,因此需要在Index.cshtml
视图中添加一个指向它的链接,如清单2-10所示。
清单2-10 在Index.cshtml
中添加一个指向RSVP表单的链接
Html.ActionLink
是一个HTML的辅助器方法(Helper Method)。MVC框架附带了一组内置的辅助器方法,它们可以方便地用来渲染HTML的链接、文本输入框、复选框、选择框以及其他种类的内容。这个ActionLink
方法有两个参数:第一个是该链接的显示文本,第二个是用户单击该链接时将要执行的动作。第21~23章将解释完整的HTML辅助器方法集合。启动该项目可以看到该辅助器所创建的链接,如图2-15所示。
如果在浏览器中将鼠标移到这个链接上,可以看到该链接指向http://< 你的服务器 >/Home/ RsvpForm。Html.ActionLink
方法已经检测了应用程序的URL路由配置,并得出/Home/RsvpForm
是一个指向HomeController
控制器上RsvpForm
动作的URL。
提示:
与传统的ASP.NET
应用程序不同,MVC的URL并不对应于物理文件。每个动作方法有它自己的URL,而MVC使用ASP.NET
的路由系统将这些URL转换成动作。
1.创建动作方法
如果单击该链接,会看到一个“404Not Found(404—未找到)”
的错误,这是因为还没有创建与这个/Home/RsvpForm
地址所对应的动作方法。此时,可以在HomeController
类中添加一个名称为“RsvpForm”
的方法来完成这一工作,如清单2-11所示。
清单2-11 在HomeController.cs
文件中添加一个新的动作方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "Good Afternoon";
return View();
}
public ViewResult RsvpForm() {
return View();
}
}
}
2.添加强类型视图
这里打算为RsvpForm
动作方法添加一个视图,但采取了稍有不同的方式——创建一个强类型视图(Strongly Typed View)。强类型视图意在渲染一个特定的域类型,而且,如果指定了想使用的类型(本例是GuestResponse
),MVC将能够创建便于使用这个类型的方法(指MVC提供了一些便捷的方法,可以在视图中方便地使用这个类型对象)。
注意:
在做进一步工作之前,要确保已编译了MVC项目。如果已经创建了GuestResponse
类,但未进行编译,MVC将不能为这个类型创建强类型视图。要编译应用程序,可以从Visual Studio的“Build(生成)”
菜单中选择“Build Solution(生成解决方案)”
(或简单地按快捷键F6)。
在代码编辑器中右击RsvpForm
方法,从弹出的菜单中选择“Add View(添加视图)”
,打开“Add View(添加视图)”
对话框,确保将“View name(视图名)”
设置为“RsvpForm”
,将“Template(模板)”
设置为“Empty(空)”
,并从“Model class(类模板)”
字段的下拉列表中选择“GuestResponse(PartyInvites. Models)”
,让“View options(视图选项)”
中的复选框处于未选状态,如图2-16所示。
单击“Add(添加)”
按钮,Visual Studio将在Views
`Home
`文件夹中创建一个新的文件,其名称为RsvpForm.cshtml
,并打开它,以便编辑。从清单2-12中可以看到它的最初内容。这是另一种结构形式的HTML文件,它包含了一个@model的Razor表达式。过一会儿便会明白,这是强类型视图,并是为视图提供便利的关键。
清单2-12 RsvpForm.cshtml
文件的最初内容
提示:
在创建视图时,所选择和勾选的选项决定了视图文件的初始内容,其他没什么作用。例如,你可以将常规视图修改成强类型视图,只需在代码编辑器中添加或去除@model指示符即可。
2.4.4 建立表单
现在已经创建了强类型视图,可以扩建这个RsvpForm.cshtml
的内容,将其制作成编辑GuestResponse
对象的HTML表单,如清单2-13所示。
清单2-13 在RsvpForm.cshtml
文件中创建表单视图
这里对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辅助器方法提供了所需的信息,使其能够理解从@model表达式所读取的属性是哪一种数据类型。
如果不熟悉C#的lambda
表达式,不用着急,第4章将提供一个概览。不过,替代运用lambda
表达式的另一种办法是将模型类型的属性名指定为一个字符串,如下所示。
...
@Html.TextBox("Email")
...
不难发现,lambda
表达式技术可以防止输错模型类型的属性名,因为Visual Studio
能够自动地弹出智能感应并选取属性,如图2-17所示。
另一个便利的辅助器方法是Html.BeginForm
,它生成一个回递给动作方法的HTML表单元素(< form >元素)。这里未给辅助器方法传递任何参数,于是它假设要回递的目标是请求此HTML文档的同一个URL。一个整洁的技巧是将它封装在一个C#的using
语句中,如下所示。
...
@using (Html.BeginForm()) {
... form contents go here ...
//... 这里是表单内容 ...
}
...
正常情况下,像这样运用using
语句,会在对象超出范围时确保对这个对象进行清理(清理对象意指收回对象所占用的资源)。例如,它通常用于数据库连接,以确保查询完成后尽快关闭这个连接。此处的using
关键词与在一个类中引用命名空间的那种using
不同。
但这里并不是清理对象(上一段文字是指“正常情况下”),而是Html.BeginForm
辅助器在它超出范围时关闭HTML的form
元素。这意味着,Html.BeginForm
辅助器方法会创建form
元素的两部分(指< form >元素的开标签和闭标签),如下所示。
如果不熟悉C#的对象清理,不必担心。这里的关键是演示如何用HTML辅助器方法创建一个表单。
2.4.5 设置启动URL
Visual Studio会以一种有助的尝试,根据当前正在编辑的视图,让浏览器请求一个URL。但这是一个不稳定的特性,在编辑其他类型的文件时就不能生效了,而且在大多数复杂的Web应用程序中,也不能在任意位置进行恰当的跳转。
为了对浏览器的请求设置一个固定的URL,可以从Visual Studio的“Project(项目)”
菜单中选择“PartyInvites Properties(PartyInvites属性)”
,然后选择“Web”
,并选中“Start Action(启动操作)”
分类中的“Specific Page(特定页面)”
选项,如图2-18所示。不必在该字段中输入一个值——Visual Studio会请求项目的默认URL,即指向Home
控制器的Index
动作方法(第15~16章将演示如何使用URL路由系统来修改这种默认的映射)。
当运行应用程序并单击“RSVP Now(现在回复)”
链接时,可以看到RsvpForm
视图中的这个表单,结果如图2-19所示。
2.4.6 处理表单
这里尚未告诉MVC,将表单递交给服务器时要做什么。此刻,单击“Submit RSVP”
按钮只是清除了表单中已经输入的值。这是因为该表单会回递给Home控制器中的RsvpForm
动作方法,这只是告诉MVC
再次渲染该视图。
注:
视图被再次渲染时,输入的数据消失了,你可能会感到奇怪。如果有这种感觉,可能是因为你一直在使用ASP.NET Web Form
开发应用程序,Web Form
在这种情况下数据是自动保留的。下面的内容将很快演示如何用MVC取得同样的效果。
为了接收并处理表单所递交的数据,这里打算使用一个聪明的特性。添加第二个RsvpForm
动作方法,以形成如下作用。
一种方法用于响应HTTP的GET请求:GET请求是某人单击一个链接时,浏览器正常发出的请求。当有人第一次访问/Home/RsvpForm
时,这个动作负责显示最初的空白表单。
一种方法用于响应HTTP的POST请求:默认情况下,用Html.BeginForm()
渲染的表单是由浏览器作为一个POST请求递交的。这个动作负责接收所递交的数据,并决定用它做什么。
以独立的C#方法分别处理GET和POST请求,有助于保持控制器代码整洁,因为这两种方法有不同的职责。这两种方法都由同样的URL进行调用,但MVC确保会根据所处理的是GET请求或POST请求,来调用合适的方法。清单2-14显示了需要对HomeController
类所做的修改。
清单2-14 在HomeController.cs
文件中添加一个支持POST请求的动作方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using PartyInvites.Models;
namespace PartyInvites.Controllers {
public class HomeController : Controller {
public ViewResult Index() {
int hour = DateTime.Now.Hour;
ViewBag.Greeting = hour < 12 ? "Good Morning" : "GoodAfternoon";
return View();
}
[HttpGet]
public ViewResult RsvpForm() {
return View();
}
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse) {
// TODO:对晚会的组织者发送Email响应
return View("Thanks", guestResponse);
}
}
}
这里在已有的RsvpForm
动作方法上添加了HttpGet
注解属性。这是告诉MVC,该方法应该仅用于GET请求。然后添加了一个重载版的RsvpForm
方法,它带有一个GuestResponse
参数,并运用了HttpPost
注解属性。该注解属性告诉MVC,这个新方法将处理POST请求。这里已经引入了PartyInvites.Models
命名空间,这样便可以直接使用GuestResponse
模型类型,而不需要使用这个类的限定名。在以下几小节中,将解释对该清单所做的添加是如何工作的。
1.使用模型绑定
第一个重载的RsvpForm动作方法渲染之前的同一个视图(RsvpForm.cshtml文件),以生成如图2-19所示的表单。
第二个重载由于其参数变得较为有趣,但已给定该动作方法是响应HTTP的POST请求而被调用的,也已给定GuestResponse
类型是一个C#类,那么这两者是如何连接的呢?
答案是模型绑定(Model Binding),这是一个非常有用的MVC特性,凭借它可以解析输入数据,并将HTTP请求中的“键/值”用来填充域模型类型的属性。这一过程与使用HTML辅助器的方法是相反的,即,在创建发送给客户端的表单数据时,生成的是HTML的input
元素,其中的id
和name
标签属性的值来自于模型类的属性名(这里描述的是HTML辅助器的作用,该作用是将模型数据转换成HTML信息,即采用“模型→HTML”的方式进行数据转换)。
与此相反,对于模型绑定,会用input
元素的名称来设置模型类实例中属性的值(其实这句话说得不完全正确,应当是用input
的元素名和值来设置模型类实例中的属性值,以便通过用户在表单的各个input
元素及其输入的值来构造一个模型类实例),然后该实例被传递给处理POST
的动作方法(这里描述的是模型绑定的作用,故模型绑定是利用表单元素及其输入的数据来创建模型对象,即采用“HTML→模型”的方式进行数据转换。所以说,模型绑定与辅助器方法的作用是相反的)。
模型绑定是一个功能强大且可定制的特性,它消除了直接处理HTTP请求的烦琐,使开发者能够使用C#对象进行工作,而不是处理Request.Form[]
和Request.QuertyString[]
的值。作为参数被传递给动作方法的GuestResponse
对象被自动地填充了表单字段的数据。第24章将深入探讨模型绑定的细节,包括如何对它进行定制。
2.渲染其他视图
RsvpForm
动作方法的第二个重载也演示了如何告诉MVC去渲染一个对请求进行响应的特定视图。以下是相关语句:
...
return View("Thanks", guestResponse);
...
这个对View方法的调用告诉MVC,查找并渲染一个名称为“Thanks”
的视图,并对该视图传递GuestResponse
对象。为了创建所指定的视图,可以右击HomeController
中的任意一个方法,并从弹出的菜单中选择“Add View(添加视图)”
,使用“Add View(添加视图)”
对话框创建一个名称为“Thanks”的强类型视图,该视图以GuestResponse
为模型类,并基于Empty模板(如果需要,请参阅前述“添加强类型视图”小节的步骤)。Visual Studio将此视图创建为Views/Home/Thanks.cshtml
。编辑此新视图,使其与清单2-15匹配,其中已加黑显示所需添加的标记。
清单2-15 hanks.cshtml
文件内容
这个Thanks视图将使用Razor并根据GuestResponse
属性的值来显示内容,GuestResponse
是在RsvpForm
动作方法中传递给View
方法的。Razor的@model表达式指示了这个强类型视图的域模型类型。为了访问这个域对象中某个属性的值,要使用Model.
< 属性名 >的方式。例如,要获得Name
属性的值,需调用Model.Name
。如果对Razor语法尚不了解,不用担心,第5章将做详细解释。
既然已经创建了Thanks视图,便有了一个以MVC处理表单的简单运行示例。在Visual Studio中启动该应用程序,单击“RSVP Now
(现在回复)”链接,在表单中添加一些数据,然后单击“Submit
RSVP``(递交回复)”按钮,便会看到如图2-20所示的结果(当然,如果姓名不是Joe,或者说不出席晚会,显示会有所不同)。
2.4.7 添加验证
现在,到了对应用程序添加验证的时候了。没有验证,用户可能会输入无意义的数据,甚至递交一个空白的表单。在MVC应用程序中,验证典型地运用于域模型,而不是用户界面。这意味着可以在一个地方定义验证条件,而在运用模型类的任何地方生效。ASP.NET MVC支持验证规则声明(Declarative Validation Rules),这是以System.ComponentModel.DataAnnotations
命名空间中的注解属性进行定义的,意即,验证约束是使用标准的C#注解属性特性来表示的。清单2-16演示了如何将这些注解属性运用于GuestResponse
模型类。
清单2-16 在GuestResponse.cs
文件中运用验证
using System.ComponentModel.DataAnnotations;
namespace PartyInvites.Models {
public class GuestResponse {
[Required(ErrorMessage = "Please enter your 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; }
[Required(ErrorMessage = "Please enter your phone number")]
public string Phone { get; set; }
[Required(ErrorMessage = "Please specify whether you'll attend")]
public bool? WillAttend { get; set; }
}
}
验证规则以黑体显示。MVC会自动地侦测这些验证注解属性,并在模型绑定过程中将它们用于验证数据。注意,该清单引入了含有验证的命名空间,因此不需要用限定名来引用它们。
提示:
正如前面曾说明的,对于WillAttend
属性,使用了可空的bool
型。这么做之后,便能够运用Required
验证注解属性了。如果使用的是一个常规的bool
型,则通过模型绑定所接收的值只能是true或false
,于是便不能判断用户是否已选择了一个值。一个可空的bool
型有3个可能的值:true、false和null
。如果用户尚未选择,系统会默认用null
来表示,这会让Required
注解属性能够报告一个验证错误。这是MVC框架如何优雅地将C#特性与HTML和HTTP相融合的一个很好的示例。
可以在控制器类中使用ModelState.IsValid
属性来检查是否有验证问题。清单2-17演示了这种事情的做法,这是在Home
控制器类的启用POST
的RsvpForm
动作方法中实现的。
清单2-17 在HomeController.cs
文件中检查表单验证错误
...
[HttpPost]
public ViewResult RsvpForm(GuestResponse guestResponse) {
if (ModelState.IsValid) {
// TODO: 给晚会的组织者发送电子邮件
return View("Thanks", guestResponse);
} else {
// 有验证错误
return View();
}
}
...
如果没有验证错误,便让MVC渲染Thanks
视图,就像前面所做的那样。如果有验证错误,则通过调用不带参数的View
方法重新渲染RsvpForm
视图。
在有错误时,仅显示表单并不十分有用,还需要给用户提供一些指示,告诉他们有什么问题,以及为什么不能接受他们所递交的表单。其实现办法是在RsvpForm
视图中使用Html. ValidationSummary
(验证摘要)辅助器方法,如清单2-18所示。
清单2-18 在RsvpForm.cshtml
文件中使用Html.ValidationSummary
辅助器方法
如果没有错误,Html.ValidationSummary
方法会在表单中以占位符的方式创建一个隐藏的列表项;否则,MVC会使这个占位符成为可见,并添加由验证注解属性所定义的错误消息。从图2-21可以看出,这是如何显示的。
直到运用于GuestResponse类的所有验证约束都得到满足,用户才会看到Thanks视图。注意,在表单中输入的数据是被保留的,而且在带有验证摘要的视图被重新渲染时,这些数据会被再次显示出来。这是模型绑定特性的另一个好处,它简化了表单数据的工作。
注:
如果曾用过ASP.NET Web Form
,应该知道Web Form
有一种服务器控件的概念。服务器控件能够保持状态,其实现方式是将序列化的值保存到一个叫做“_
VIEWSTATE”的隐藏字段中。
ASP.NET MVC的模型绑定与Web Form的服务器控件、回递或视图状态等概念无关。
ASP.NET MVC不会将隐藏字段
_VIEWSTATE
注入到所渲染的HTML页面。
高亮显示无效字段
创建文本框、下拉列表以及其他元素的HTML辅助器方法有一个十分灵活的特性,可以用来与模型绑定相关联。上述保留用户表单输入数据的同样机制也可以用来高亮验证检查失败的个别字段。
当模型类的某个属性验证失败时,HTML辅助器方法会生成稍有不同的HTML。例如,以下是Html.TextBoxFor(x = > x.Name)
在没有验证错误时生成的HTML。
< input data-val="true" data-val-required="Please enter your name"id="Name" name="Name"type="text" value="" / >
而当用户未提供一个值时(这是一个验证错误,因为在GuestResponse
模型类中的Name属性上运用了Required
注解属性),以下是同样的调用所生成的HTML。
< input class="input-validation-error"data-val="true" data-val-required="Please enteryour name"id="Name" name="Name" type="text" value="" / >
这里以粗体高亮了其差异:辅助器方法给input元素添加了一个值为“input-validation-error”
的class标签属性。通过创建样式表便可以利用这一特性,该样式表可以为这个class以及其他HTML辅助器方法所生成的class设置一些不同的CSS样式(这样就可以对不同的class值设置一些不同的显示效果)。
MVC项目的约定是:将静态内容(如CSS样式表等)放在名称为Content的文件夹中。在“Solution Explorer
(解决方案资源管理器)”中右击“PartyInvites”
项目,从菜单中选择“Add(添加)”→“New Folder(新文件夹)”
,将其命名为“Content”
,便可以创建该文件夹。
为了创建CSS文件,右击新建的“Content”文件夹,从菜单中选择“Add(添加)”→“NewItem(新项)”
,在项目模板集合中选择“Style Sheet(样式表)”
。将此文件的名称设置为“Styles.css”
,如图2-22所示。
单击“Add(添加)”
按钮,Visual Studio便会创建“Content/Styles.css”
文件。设置新文件的内容,使其与清单2-19吻合。
清单2-19 Styles.css
文件的内容
.field-validation-error {color: #f00;}
.field-validation-valid { display: none;}
.input-validation-error { border: 1px solid #f00; background-color:#fee; }
.validation-summary-errors { font-weight: bold; color: #f00;}
.validation-summary-valid { display: none;}
为了使用该样式表,可以在RsvpForm
视图的头部(< head >元素中)添加一个新的引用,如清单2-20所示。给视图添加link
元素与在常规的静态HTML文件中的做法一样。不过,第27章将演示捆绑包(Bundle)特性,它能够对JavaScript和CSS样式表进行整理,并能通过一个单一的HTTP请求将这些内容发送给浏览器。
提示:
可以从“Solution Explorer
(解决方案资源管理器)”中拖曳JavaScript和CSS文件,将它们放到代码编辑器中。Visual Studio能够对所选的这些文件自动地创建script
和link
元素。
清单2-20 在RsvpForm.cshtml
文件中添加Link元素
提示:
从MVC 3直接转向MVC 5的人可能会认为,为了在视图中添加CSS文件,需要将这里的href标签属性指定为@Href("~/Content/Site.css")
或@Url.Content("~/Content/Site.css")
。但自从MVC 4开始,Razor已经能够检测以“~/”开始的标签属性,并自动地插入@Href或者@Url调用。
利用该样式表,在递交会引发验证错误的数据时,会显示更为明显的验证错误,如图2-23所示。
2.4.8 设置内容样式
至此,除了即将要实现的发送电子邮件功能以外,该应用程序的基本功能也已就绪,但其整体外观很糟糕。虽然这是一本专注于服务器端开发的书籍,但是微软公司已经采纳了许多开源库,并将它们纳入到了Visual Studio的项目模板之中。
笔者不是这些模板的“粉丝”,但确实喜欢其中的一些库,其中之一便是MVC 5新采纳的Bootstrap。它是最初由Twitter开发的一款很好的CSS库,如今Twitter的运用已经十分广泛了。
当然,不一定要通过Visual Studio的项目模板来使用Bootstrap等这样的一些库,可以直接从项目网站下载其文件或使用NuGet进行下载。NuGet已经被集成到了Visual Studio之中,它可以访问预先打包的软件目录,并自动地对这些软件包进行下载和安装。NuGet最好的特性之一是它可以管理各个包之间的依赖项,例如,如果安装Bootstrap,则NuGet也会下载并安装Bootstrap特性所依赖的jQeury。
1.使用NuGet
安装Bootstrap
为了安装Bootstrap
包,可以从Visual Studio的“Tools(工具)”菜单中选择“Library Package Manager
(库包管理器)”→“Package Manager Console
(包管理器控制台)”,这会打开NuGet
命令行窗口。在其中输入以下命令并按Enter键。
Install-Package -version 3.0.0 bootstrap
“Install-Package”命令指示NuGet下载一个包及其依赖项,并将它们添加到项目。这里所需的包名称为“bootstrap”。另外,可以通过NuGet网站(http://www.nuget.org)对包进行搜索,也可以使用Visual Studio的NuGet
用户界面进行搜索,其办法是在Visual Studio中选择“Tools(工具)
”→“Library Package Manager
(库包管理器)”→“Manage NuGet Packages for Solution
(管理解决方案的NuGet包)”。
这里使用了“-version”
,用以指示所需的Bootstrap版本为3,它是编写本书时的最新稳定版。如果不设定“-version”
,NuGet
会下载该包的最新版本,但这里希望能够确保按演示的那样再现示例,因此安装指定版本有助于保证一致性。
NuGet
将下载Bootstrap
及其依赖项jQuery
所需的全部文件。其中的CSS文件被添加到“Content”文件夹,同时会创建“Scripts”
文件夹(这是MVC放置JavaScript文件的标准位置),并在其中放置Bootstrap和jQuery文件。同时也会创建一个“fonts”文件夹,这是Bootstrap
特性的一个“癖好”——期望一些文件放在特定的位置。
注:
本章之所以演示Bootstrap,是为了说明MVC框架生成的HTML可以轻而易举地用于一些流行的CSS和JavaScript库。然而,本书不想偏离服务器端开发的主题,但如果需MVC框架的客户端开发方面的完整信息,可参阅笔者著的Pro ASP.NET MVC 5 Client,该书于2014年由Apress出版。
2.设置Index视图Bootstrap
的基本特性是通过运用于HTML元素的class起作用的,这些class与放在Content文件夹中的样式表文件所定义的CSS选择器相关联。Bootstrap所定义的全部class细节可以从http://getbootstrap.com处获取,但也可以从清单2-21看出如何将一些基本的样式运用于Index.cshtml
视图文件。
清单2-21 在Index.cshtml
文件中添加Bootstrap
这里已添加了link
元素,以引用Content文件夹中的bootstrap.css
和bootstrap-theme.css
文件。这些是为引用Bootstrap库提供的CSS基本样式所需的文件,另外在Scripts文件夹中还有一个相应的JavaScript文件,但本章不需要用到它。此处还定义了一个style
元素,在其中设置了body
元素的背景色和一个用于a元素的文本样式。
提示:
你可能会注意到,Content
文件夹中的每一个Bootstrap文件都有一个带有“min”前缀的配对文件,例如,bootstrap.css和bootstrap.min.cs
。将应用程序部署到产品环境中时,这是压缩JavaScript和CSS文件的通用做法,即它是一个删除所有空格符的过程。就JavaScript而言,它会用较短的标签来替换函数及变量名。这种压缩的目的是为了减少对浏览器递送内容时所需的带宽量。第27章将描述自动管理这一过程的ASP.NET特性。对于本章以及本书的大多数章节而言,将使用其中的常规文件,这是在开发和测试期间的常规方法。
引入Bootstrap样式并定义一些自己的样式后,需要对自己的元素设置样式。这是一个简单的示例,因此只需要使用3个Bootstrap CSS的class:text-center、btn和btn-success
。text-center
可以将元素及其子元素的内容居中;btn可以将按钮、input或a元素的样式设置为漂亮的按钮,而btn-success
则可以指定按钮所用的颜色范围。按钮的颜色取决于所用的主题,这里使用了默认主题(由bootstrap-theme.css
文件进行定义),但通过在线搜索,有无数的替代品。可以从图2-24中看到所形成的效果。
显而易见,笔者不是一名Web设计师。事实上,当笔者还是一个孩子时,就经常逃艺术基础课,这足以说明笔者缺少艺术天分。结果笔者在数学课程上有了更多的快乐时光,但这也意味着笔者的艺术技能未超过10岁儿童的平均水平。在实际项目中,笔者会寻求专业的帮助来设计和设置内容的样式。不过,在这个例子中笔者打算独自完成,尽可能地用Bootstrap
的约束和一致性。
3.设置RsvpForm
视图的样式
Bootstrap定义了一些能够运用于设置表单样式的class。这里不打算详细叙述,但可以从清单2-22中看出如何运用这些class。
清单2-22 在RsvpForm.cshtml
文件中添加Bootstrap
本例中Bootstrap的class
创建了一个带标题的面板(panel),目的是提供结构化的布局。为了设置表单的样式,这里使用了form-group
,用以设置其中label
及其关联的input
及select
等元素的样式。
其中的一些元素是使用HTML辅助器方法创建的,意即,这不是一些静态预定义的元素,以便能够运用所需的form-control
的class
。但幸运的是,辅助器方法带有一个可选的对象参数,该参数可以在其所创建的元素上指定标签属性,如下所示。
...
@Html.TextBoxFor(x = > x.Name, new { @class = "form-control"})
...
这里使用了C#的匿名类型特性创建这一对象,匿名类型将在第4章中进行描述。这里的作用是指示,应该在这个TextBoxFor
辅助器生成的元素上将class属性设置为“form-control”
。由该对象定义的属性用于对HTML元素添加标签属性,class是C#语言的保留字,因此使用了前缀@。这是一个标准的C#特性,以便在表达式中能够使用关键字。可以从图2-25看到该样式的结果。
4.设置Thanks视图的样式
要设置样式的最后一个视图是Thanks.cshtml
,从清单2-23可看出其做法。可以看出,所添加的标记类似于Index.cshtml
视图。为了使应用程序易于管理,一个很好的原则是应当尽可能避免代码和标记的重复。第5章将介绍Razor的布局(Layout),而第20章将描述分部视图(Partial View)。这两种方法均可以用来减少标记重复。
清单2-23 将Bootstrap
运用于Thanks.cshtml
文件
lead
是Bootstrap
的一种印刷符号样式,从图2-26可以看到其效果。
2.4.9 完成示例
该示例应用程序的最后需求是,将已完成的RSVP用电子邮件发给晚会的组织者。其实现办法是添加一个动作方法,以便使用.NET框架的E-mail类来创建并发送一份邮件消息——这恰好是最符合MVC模式的一种技术。但是,这里打算使用WebMail
辅助器方法。这种办法不属于MVC框架部分,但它确实能够完成这个示例,而不必建立另一个发送邮件的表单。这里希望在渲染Thanks视图时发送这份邮件消息。清单2-24显示了需要进行的修改。
清单2-24 在Thanks.cshtml
文件中使用WebMail
辅助器
注:
这里使用WebMail
辅助器方法是因为它可以轻松地演示发送一份邮件消息。然而,笔者通常更喜欢将该功能放在一个动作方法之中。第3章将在描述MVC体系结构模式时,对此进行解释。
清单2-24添加了一段Razor代码块,它用WebMail
辅助器来配置邮件服务器的细节,包括服务器名、服务器是否需要SSL连接,以及账号细节等。一旦配置了所有这些细节,就可以用WebMail.Send
方法来发送这封邮件了。
这里将所有关于e-mail的代码都封装在一个try...catch块中,于是,如果不能发送邮件,可以向用户发出警告。这里的办法是在Thanks视图的输出中添加一个文本块。一个更好的办法是,当不能发送邮件消息时,显示一个独立的错误信息视图,但这里希望第一个MVC应用程序尽量简单。