本节书摘来自异步社区《ASP.NET MVC 4 实战》一书中的第2章,第2.3节,作者: 【美】Jeffrey Palermo , 【美】Jimmy Bogard , 【美】Eric Hexter , 【美】Matthew Hinze , 【英】Jeremy Skinner,译者: 徐燕萍 , 李萍 , 林逸 , 更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.3 Guestbook示例应用程序
ASP.NET MVC 4 实战
为使Guestbook应用程序有用,我们需要为用户提供一些递交条目的方式,将这些条目存储起来,以便稍后进行查看。为了达到这一目标,我们打算对应用程序添加一个数据库,将其作为该留言簿的后台存储库。
首先从创建数据库开始,然后考察如何接受用户输入并存储它,最后演示如何获取数据,以便将其显示给用户。
2.3.1 创建数据库
绝大多数Web应用程序都需要某种数据仓库的支持,这可能是一个关系型数据库(如微软的SQL Server或MySQL),或一个文档数据库(如Raven DB、MongoDB,或CouchDB),抑或是一个简单的XML文件。我们的应用程序将使用SQL Server Compact,这是微软最新添加到SQL Server家族的一个关系型数据库。
SQL Server Compact是一个新的轻量级数据库,可用于Web和桌面应用程序。与完整版本的SQL Server不同,它不需要安装任何服务器软件来运行。这意味着它是bin可部署的(Bin-Deployable),意即,只需要将相应的DLL文件添加到你应用程序的bin文件夹,就可以使用SQL Server Compact数据库了。这种方法的最大好处是,你可以将SQL Server Compact数据库部署到任何运行.NET 4的虚拟主机上,而无须虚拟主机提供商安装任何东西。
首先,鼠标右键单击App_Data目录并选择“添加”(Add),然后选“新项”(New Item)。这将打开“添加新项”(Add New Item)对话框,在这里你可以选择SQL Server Compact Database,如图2.8所示。
将此数据库命名为Guestbook.sdf,并点击“添加”(Add)按钮。
注:如果在“添加新项”对话框中看不到SQL Server Compact数据库项目,可能是由于你尚未为Visual Studio安装SQL Server Compact工具。请确保已经安装了Visual Studio 2010的Service Pack 1(Visual Studio SP1升级包)。
下一步将对数据库添加一个数据表。为此,在“服务器资源管理器”(Server Explorer)中双击新建的Guestbook.sdf数据库,以打开它。现在,鼠标右键单击“服务器资源管理器”中的“表”(Table)选项,并选择“创建表”(Create Table),如图2.9所示。
点击该菜单会打开“创建表”对话框。在该对话框中将表名设置为GuestbookEntries。这个表将用来为留言簿存储条目,因此它需要几个数据列,这些列包括注册到留言簿的人名以及他们的消息。我们还需要一个Id列作为该表的主键,以及一个添加消息的日期列。为了确保插入一行之后数据库会自增Id列,我们需要将Id的Identity属性设置为True。该表的定义如图2.10所示。
一旦已经创建了数据表,便需要对应用程序添加一些类,以表示留言簿条目的内容。这些类将形成应用程序的模型。
2.3.2 添加模型
Guestbook应用程序的模型非常简单—一个表示留言簿条目的类就是所需要的全部。这个类叫作GuestbookEntry,将它添加到项目的Models目录,并对它添加几个属性:
public class GuestbookEntry
{
public int Id { get; set; }
public string Name { get; set; }
public string Message { get; set; }
public DataTime DateAdded { get; set; }
}
这个模型很简单—它只是一个含有4个属性的POCO对象(POCO,Plain Old CLR Object—旧式无格式公共语言运行时(CLR)对象。很多人不太理解POCO对象,其实这种对象就像文本文件一样,是一种最简单、最原始、不带任何格式的对象。因此,在各种环境中最容易对这类对象进行处理,包括用各类语言进行处理—译者注),这些属性对应于数据表中的各个列。我们打算使用这个类的实例来表示存储在数据库中的数据,但是,如何将数据库中的数据转换成对象?我们可以根据SQL查询的结果,手工地编写必要的代码来合成GuestbookEntry的实例。不过,依靠对象关系映射(Object-Relational Mapping ,ORM)工具做这件事要更简单些。
对于本应用程序,我们将使用Entity Framework 4.1来做这种映射—尽管在.NET平台上还有许多其他ORM工具可以选择(我们将在第15章考察另一个ORM工具—NHibernate)。虽然Entity Framework是一个大到足以要好几本专著才能对它进行描述的论题(如Julia Lerman所著的Programming Entity Framework(《实体框架编程》),以及由Stefano Mostarda、Marco De Sanctis和Daniele Bochicchio所著的Entity Framework 4 in Action(《Entity Framework 4实战》)等),但Entity Framework 4.1含有一个简化的API,它为使用Entity Framework执行数据访问提供了简单的方式。
为了利用Entity Framework,我们将在应用程序中添加一个DbContext类。这个DbContext提供了一个对Entity Framework的抽象,让我们能够进行数据持久化并接受数据。我们将创建一个叫作GuestbookContext的类,它也驻留在应用程序的Models目录中。该类的实现如清单2.3所示。
数据访问(方法的)选择
在.NET应用程序中执行数据访问有许多选择。许多现代应用程序都使用诸如NHibernate或Entity Framework之类的工具来访问关系型数据库,但这些并不是唯一的选择。如果应用程序很小,你或许会决定不需要ORM的这种额外复杂性,在这种情况下,一种更简单的工具,如WebMatrix.Data或Simple.Data,可能就足够了。
WebMatrix.Data是和ASP.NET MVC 3同一时间由微软作为ASP.NET Web Pages的套件产品发布的,它利用原始的SQL语句和DLR的动态类型,为执行数据访问提供了一种轻量级的手段。Simple.Data提供了一个类似的解决方案,但需要依靠动态查询语法而不是SQL字符串。Simple.Data的更多信息可以在https://github.com/markrendle/Simple.Data中找到。
清单2.3 用于与数据库进行交互的DbContext
这个类继承于DbContext基类(位于System.Data.Entity命名空间),它首先声明了一个无参数的构造器,并使用构造器链(Constructor Chaining)将数据库名称传递给基类。在此例中,因为数据库叫作Guestbook.sdf,故将字符串“Guestbook”传递给基类的构造器。如果不这么做,Entity Framework会默认使用上下文类的全类型名作为该数据库的名称(注意,这个类位于Guestbook.Models命名空间,故它的全类型名为Guestbook.Models.GuestbookContext —译者注),因而会查找一个名为Guestbook.Models.GuestbookContext.sdf的文件。
该上下文类也定义了一个单独的属性Entries,其类型是DbSet。这个属性所起的作用是,作为在GuestbookEntries表中查询数据的集合,就好像它是内存中的一个对象集合一样。在后台,Entity Framework将生成适当的SQL来查询该表,并将结果转换为强类型的GuestbookEntry对象。我们将在2.3.4小节看看如何对这个集合进行查询。
最后,我们需要告诉Entity Framework,让它与SQL Server Compact数据库进行对话(默认情况下,Entity Framework会尝试连接到SQL Server Express的实例数据库)。
通过模型生成数据库
在这个例子中,我们首先创建了数据库,目的是展示Visual Studio对SQL Server Compact所具有的设计时支持。但是,先创建数据库并不是必需的(以上所采用的做法即所谓Database First(数据库先行)—译者注)。
如果你想使用模型而不先创建数据库,Entity Framework有足够的智能识别这种情况,并在你第一次使用模型时,尝试为你创建数据库、表和列(这种做法即所谓Code First(代码先行)—译者注)。
为了做到最后一步,我们需要对应用程序添加一些初始化代码。有几种方法可以实现这一目的。第一种是在Global.asax.cs文件的Application_Start方法中手工添加代码。该方法是一个在应用程序启动时只运行一次的特殊方法(典型地发生在第一次访问Web服务器时)。然而,代替这一做法,我们将采取一种稍有不同的办法—用一个NuGet包来为我们添加初始化代码。
NuGet是一个“包管理器”(Package Manager)工具,它能够将开源库快速而简便地添加到任何.NET项目。尽管NuGet未与ASP.NET MVC项目绑在一起,但它是作为ASP.NET MVC安装程序的一部分交付的,因此随时可以使用它,而不必进行单独的安装。这一功能在一个叫作EntityFramework. SqlServerCompact的包中,通过右击项目中的“引用”(References)节点,并选择“管理NuGet包”(Manage NuGet Packages)便可以安装它,如图2.11所示。
这会打开一个对话框,在其中你可以搜索NuGet陈列库中的包。做法是,点击屏幕左侧的“在线”(Online)项,然后在右上角的搜索框中键入EntityFramework.SqlServerCompact,如图2.12所示。这应该能定位到一个单一的匹配,然后通过点击“安装”(Install)按钮便可以安装。
注:和“管理NuGet包”对话框一样,NuGet在Visual Studio中也包括了一个命令行外壳程序,可以通过点击“视图”(View)→“其他窗口”(Other Windows)→“包管理器控制台”(Package ManagerConsole)启动它。你可以不使用图2.12所示的GUI(图形用户界面)而使用这个控制台,通过发出Install-Package命令来安装这个包。
一旦安装完毕,这个包便会自动地将相关代码添加到项目,以对使用SQL Server Compact数据库的Entity Framework进行配置。
使用WebActivator注册启动代码
EntityFramework.SqlServerCompact包内在地依赖于另一个名为WebActivator的包,以生成相应的启动代码。WebActivator是由微软ASP.NET团队的一位开发人员—David Ebbo创建的。它提供了一种方便的方法,可以为应用程序添加初始化代码,而不必让这些代码与Application_Start方法混杂在一起(可见这种办法可保持Application_Start方法整洁—译者注)。
当使用依赖于WebActivator的包时,它会在App_Start目录中生成初始化代码,这些代码会和WebActivator程序集的引用一起被添加到应用程序中。
WebActivator本身是一个开源项目,因此,如果你好奇它的工作机制,可以从https://bitbucket.org/davidebbo/webactivator下载其代码。
这个新建的模型类将形成应用程序的核心,但我们需要一些让应用程序的用户创建模型实例的方式,以便能够将用户的评论持久化到数据库中。为了做到这一点,我们将对应用程序添加一个控制器,它负责接受用户输入。
2.3.3 接受留言簿条目
为了接受新的留言簿条目,我们将对应用程序添加一个新的控制器。这可以通过右击Controllers目录,并选择“添加”(Add)→“控制器”(Controller)来完成。这会弹出“添加控制器”(Add Controller)对话框,如图2.13所示。将该控制器命名为GuestbookController。
“添加控制器”对话框提供了定制控制器的能力—“模板”(Template)下拉菜单让你选择是否将控制器生成为一个空类(默认选项),或是自动地生成一些通用场景。其中两个选项为。
带有读/写动作和视图的控制器—这一选项将生成控制器动作方法,以及提供了使用Entity Framework进行简单CRUD(创建、读取、更新、删除)操作屏幕的视图(马上做详细讨论)。
带有空读/写动作的控制器—这一选项将为CRUD场景生成控制器动作,但是不生成任何视图,也不使用任何特定的数据访问技术。
现在,我们将使用默认的“空控制器”(Empty Controller)模板。
点击“添加”(Add)按钮之后,将在Visual Studio编辑器中打开这个新控制器。我们首先对该控制器添加一个叫作Create的新动作,如清单2.4所示。
清单2.4 带有Create动作的GuestbookController
using System.Web.Mvc;
namespace Guestbook.Controllers
{
public class GuestbookController : Controller
{
public ActionResult Create()
{
return View();
}
}
}
这个Create动作简单地返回了一个ViewResult,通过使用View方法指示框架应该渲染一个Views/Guestbook子目录中名称为Create.cshtml的视图。
如果此时你试着在浏览器中输入http://localhost:<端口号>/Guestbook/Create网址来访问这个动作,你将会看到一条报告视图找不到的错误消息,如图2.14所示。
该错误消息显示了框架的搜索路径,以试图为这个Create动作找到视图。注意,它在几个子目录中查找了带有不同文件扩展名的视图(带有.aspx/ascx扩展名的文件是旧式Web Form视图引擎所使用的,这是ASP.NET MVC 1、2下编写视图的方式)。
按约定,框架会在专用于控制器的子目录中搜索视图(其含义为,在Views/<控制器名>目录中搜索用于该控制器的视图—译者注),如果找不到视图,会退回Views/Shared文件夹进行查找,这是存放由多个控制器使用的视图的地方。
为了阻止这种错误发生,我们可以添加这个视图,在GuestbookController中右击Create动作,并选择“添加视图”(Add View),如图2.15所示。
点击该菜单项将弹出“添加视图”对话框,如图2.16所示。保留所有的默认选项,并点击“添加”(Add)按钮。
利用这个新加的Create.cshtml文件,我们可以添加一些让用户递交留言簿条目的标记,如清单2.5所示。
清单2.5 Create视图的内容
该视图包含了一个简单的HTML表单,让用户输入姓名和消息,并把它们回递给Create动作。注意,form元素的名称是Name和Message(应当是此表单的两个文本输入框(<input />)
的name属性分别是Name和Message,而不是form元素的名称—译者注),它们与GuestbookEntry对象所定义的属性匹配。这是便于自动数据绑定所必需的,我们等一会儿解释。
这个新的Create动作可以通过http://localhost:<端口号>/Guestbook/Create进行访问。最终结果如图2.17所示。
现在,我们需要创建一个控制器动作来处理这个表单的递交,并将数据插入数据库。为了做到这一点,需要利用前面所定义的GuestbookContext和GuestbookEntry类。首先对GuestbookController添加一个新的Create过载动作,如清单2.6所示。
清单2.6 使用一个控制器动作处理表单数据
第二个Create过载动作用HttpPost注解属性进行修饰,这确保该动作版本只能作为对表单递交的响应进行调用(这称为动作方法选择器(Action Method Selector),将在第16章做深入考察)。它还接受一个GuestbookEntry类型的参数,其属性将自动用表单数据进行填充,因为清单2.5的表单字段名与这些属性名匹配。这一过程叫作模型绑定(Model Binding),我们将在第10章加以探讨。
在这个Create动作中,在保存GuestbookEntry实例之前,我们可以对该实例进行进一步操作(此例中对DateAdded属性设置了当前日期和时间)。通过将该对象添加到GuestbookContext上的EntriesDbSet,我们首先保存了该对象(实体框架知道它需要跟踪这条新条目),然后对SaveChanges的调用会将此新条目写入数据库。
就其本身而言,能够提交消息并不十分有用。让我们看看如何还能提供一种方式,以列出已保存的消息。
2.3.4 显示留言簿条目
为了显示留言簿条目,我们将对GuestbookController添加一个Index动作,它将利用GuestbookContext来接收20条最新的条目,并将它们传递给视图。以下是更新后的GuestbookController。
清单2.7 添加Index动作
新的Index动作首先定义了一个查询,以接收20条最新的条目,这是通过按条目的添加日期顺序进行排序,然后取最前面20条的方式实现的。然后该查询被执行(注意,是查询定义,而在中对该查询调用ToList时该查询才会执行—译者注),而结果被保存在ViewBag中,以便能够在视图中对它们进行访问。我们也修改了Create动作,以便一旦创建了一条新条目,便重定向到Index动作。这是通过使用RedirectToAction方法来实现的,它指示框架应该执行一个HTTP 302的重定向,以便将浏览器发送到一个不同的位置。
语言集成查询(LINQ)
清单2.7所示的Index动作中的查询是使用语言集成查询(LINQ)语法进行定义的,LINQ是在.NET 3.5中作为C#3的一部分首次引入的。LINQ提供了一种定义强类型查询的方式,可以针对各种不同的数据源执行。
在本例中,Entity Framework的LINQ提供程序会将该查询转换成接收SQL Server Compact数据库数据所必需的相应SQL语句。
我们还需要为该动作创建相应的视图。同样,该视图的创建可以在Index动作上右击,并选择“添加视图”(Add View),以便在适当的位置创建一个Index.cshtml文件。该视图的代码如清单2.8所示。
清单2.8 显示留言簿条目
除了含有一个添加新条目的链接外,这个视图循环遍历了先前添加到ViewBag的每个条目,并写出了消息、作者姓名和消息日期。通过导航到新动作/Guestbook/Index,你可以看到其结果,如图2.18所示。
我们现在已经完成了Guestbook应用程序的基本功能—可以提交和查看条目。但还有很多事情可以做。作为初步尝试,我们可以删除标题条的“My MVC Application”消息。
2.3.5 用布局定制外观
我们此刻所见到的视图只包含了特定于单个页面的内容。所有外围元素(如菜单和标题)都是在一个布局中定义的。布局(Layout)可以用来提供由所有页面共享的公用用户界面元素。(如果你曾使用过以前版本的ASP.NET MVC或ASP.NET Web Form,那么,布局类似于母版页(Master Page)。)让我们看看如何修改布局,以便为查看留言簿条目时,显示一个更好的应用程序标题和附加的菜单项,如图2.19所示。
为了编辑应用程序的布局,可打开位于ViewsShared子目录中的_Layout.cshtml文件。该文件内容显示在清单2.9中。
清单2.9 默认布局
在顶部,该布局包含了CSS和脚本的导入。Site.css文件中含有用于应用程序的样式,而脚本元素包括了流行的jQuery库,我们可以将其用于添加与页面的富客户端交互性(第7章将详细探讨jQuery)。
为了修改应用程序标题,我们可以用自己的字符串代替<h1>
元素的内容(本例使用了“My Guest Book”)(清单2.9未列出这一部分—译者注)。
在这个文件中还有一些其他有趣的东西。默认应用程序中的“登录”(Log On)链接是通过分部视图(Partial View)渲染出来的。我们将在第3章考察分部视图,但它们主要是提供一种跨越多个页面重用部分HTML的方式。
该文件也包含了应用程序的菜单。它被渲染成一个无序列表,这些列表项包含了指向各个动作的链接。为了渲染指向特定控制器动作的超链接,我们可以使用HTML辅助器ActionLink,而不是使用硬编码的链接。同样,我们将在下一章探讨这些辅助器,但现在可以开始利用它们。为了对留言簿条目列表添加一个新的菜单项,我们对菜单简单地添加了以下代码:
<li>@Html.ActionLink("View Entries", "Index", "Guestbook")</li>
这将生成一个指向条目页面的新链接—该方法的第一个参数是超链接的文本,第二个是要链接到的动作名称,第三个是动作所在的控制器名称。
此文件中的最后一个有趣的东西是对RenderBody的调用。该方法将注入当前视图的内容,以便让布局包围住前面所编写的专用于动作的视图所生成的标记。
有了上述适当就位的新页面标题和菜单项,我们已经做好了继续工作的准备。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。