注:为保证可读性,文中Controller、View、Model、Route、Action等ASP.NET MVC核心单词均未翻译。
迭代1 - 创建应用程序
在这个系列中,我们将从头至尾的创建一个Contact Management应用程序。我们可以通过它来管理亲戚、朋友、同事的联系信息,如名字、电话号码、电子邮件地址等等。
我们将通过迭代的方式开发这个应用,并在每次迭代的过程中逐渐的扩展和改善该应用程序。
本次迭代
在这第一次迭代中,我们将用最快最简单的方式建立起Contanct Manager最基本的应用程序。在接下来的迭代中,我们将逐渐改善应用程序的设计。
Contact Manager是一个基本的数据库驱动的应用程序。你可以使用它建立新的联系人,编辑已存在的联系人亦或者删除这些联系人。
本次迭代我们将完成如下步骤所述的内容:
- 建立ASP.NET MVC应用程序。
- 建立数据库。
- 使用AEF建立实体模型。
- 建立一个controller action以及一个view来列出所有数据库中存在的联系人。
- 同样,建立controller action以及view向数据库添加新的联系人。
- 建立controller action以及view 实现编辑数据库中现有的联系人记录。
- 建立controller action以及view用以实现对数据库中已存在的联系人的删除操作。
开发环境
我的开发环境是ASP.NET MVC Framework RC2,Visual Studio 2008SP1,SQL2005SP2
具体的安装方法什么的这里就略掉了,前人已经说过太多了。你可以去重典、Q.Lee.Lulu的Blog去看看。
建立ASP.NET MVC 项目
运行VS2008 选择文件->新项目。在新建项目(图1)对话框中选择项目类型为Web中的ASP.NET MVC Web Application模板并命名为“ContactManager”。然后点击确认。
图1
随后将出现建立测试项目对话框(图2)。当你建立ASP.NET MVC应用程序的时候,你可以通过这个对话框为你的解决方案指定及添加一个测试项目。由于我们打算在接下来的某次迭代中使用单元测试,所以这里我们点击“是,建立一个单元测试项目”。在ASP.NET MVC项目建立初期创建单元测试要比在该应用建立后再创建单元测试方便得多。
图2
至此ASP.NET MVC应用程序会出现在你的解决方案资源管理器中(图3)。这时解决方案中应当包含两个项目:一个名称为ContactManager的ASP.NET MVC项目及一个叫做ContactManager.Tests的单元测试项目。
图3
删除项目中的示例文件
默认的ASP.NET MVC项目模板中包含了若干controller及views的示例文件。在这里我们应当从项目中删除这些文件:
\Controllers\HomeController.cs
\Views\Home\About.aspx
\Views\Home\Index.aspx
然后在测试项目中删除如下文件:
\Controllers\HomeControllerTest.cs
新建数据库
在Contact Manager这个项目中,我们需要使用数据库来存储联系人信息。ASP.NET MVC framework可以与很多流行的数据库协同工作,如Microsoft SQL Server、Oracle、MySQL、IBM DB2等,在本项目中我们将使用Microsoft SQL Server作为数据库。不知你是否还记得,在安装Visual Studio时,安装程序提供了安装Miscrsoft SQL Server Express的选项。接下来我们就将使用这个免费提供的MS SQL文件型数据库。
右键点击解决方案浏览器中的App_Data文件夹,选择添加->新建项。在添加新项对话框中选择类型为数据中的Sql Server数据库模板(图4)。将其命名为ContactManagerDB.mdf并点击确认。
图4
然后新创建的数据库就已经出现在解决方案资源管理器中的App_Data文件夹下了。双击ContactManager.mdf文件打开服务器资源管理器窗口并连接数据库。
你可以通过服务器资源管理器窗口建立新的数据库对象,包括表、视图、触发器及存储过程等。右击“表”文件夹然后选择“添加新表”。这时会出现数据库表设计器。(图5)
图5
我们需要按照下表设计数据库字段
列 |
数据类型 |
允许Null |
Id |
int |
false |
FirstName |
nvarchar(50) |
false |
LastName |
nvarchar(50) |
false |
Phone |
nvarchar(50) |
false |
|
nvarchar(255) |
false |
第一列,即Id列比较特殊。你需要将其设置为标识列并将其设置为主键。设置为标识的方法为在列属性(看图5下半部分)中将“是标识”的值更改为“是”。右击Id列并选择“设置主键”即可将该列设置为主键。被标识为主键的列前边会出现一个钥匙图标(图5)。
完成表的设计以后,点击保存按钮保存这个新建的表并将其命名为“Contacts”
然后你需要为这个表添加一些数据进去。在数据库资源管理器窗口中右击Contacts表然后选择“显示表数据”菜单并在出现的表格中输入一条或多条数据。
建立数据模型
ASP.NET MVC 应用程序由模型,视图及控制器组成。我们首先建立一个数据模型来描述上一节创建Contacts表。
在本系列中,我们采用Microsoft ADO.NET Entity Framework(以下称AEF)基于表结构自动创建模型。
ASP.NET MVC framework并不是和AEF紧密相连的。你大可使用其他的数据库存储技术如NHibernate、LINQ to SQL、ADO.NET等。
我们通过以下几步创建数据模型:
- 在解决方案资源管理器中右击Models文件夹,选择添加->新建项。(图6)
- 选择类型中的数据,然后选择ADO.NET Entity Data Model模板。将其命名为“ContactManagerModel.edmx”后点击添加按钮,而后会出现实体模型向导(图7)。
- 选择模型内容步骤中,我们选择从数据库生成(图7)。
- 选择您的数据连接步骤中,选择下拉列表框中的ContactManagerDB.mdf数据库并将实体连接设置命名为“ContactManagerDBEntities”(图8)。
- 在选择数据库对象步骤中,选中“表”复选框(图9)。这样创建的数据模型将会默认包含数据库中的所有表(当然,现在只有Contacts这一个表)。在模型命名空间文本框中输入“Models”。最后点击完成按钮关闭实体模型向导。
图6
图7
图8
图9
按照实体模型向导一步步设置完成过后,就可以使用实体数据模型设计器对实体模型进行编辑了。设计器中会显示根据数据库中每一个表模型化的对象(类)。这里你应该看到一个叫做Contacts的类。
实体数据模型向导基于数据库中表的名称创建这些模型类。你通常需要更改这些由向导生成的类名。在设计器中右击Contacts类并选择重命名.将该类的名称从Contacts(复数形式)改成Contact(单数形式)。修改后的类应该如图10所示。
图10
至此我们已经将数据模型建立完毕。我们可以使用Contact类描述数据库中特定的联系记录。
创建Home Controller
接下来我们开始着手建立Home controller。Home controller是ASP.NET MVC应用程序中默认执行的controller。
要建立Home controller,我们只需在解决方案资源管理器中的Controllers文件夹上右击,在弹出的菜单中选择添加->Controller(图11)。需要注意的是下面的复选框Add action methods for Create, Update, and Details scenarios.对于这个项目来说,在点击Add按钮之前需确认他们已经被选中了。
图11
建立Home controller后,该类会默认添加如下代码段
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Mvc.Ajax; namespace ContactManager.Controllers { public class HomeController : Controller { // // GET: /Home/ public ActionResult Index() { return View(); } // // GET: /Home/Details/5 public ActionResult Details(int id) { return View(); } // // GET: /Home/Create public ActionResult Create() { return View(); } // // POST: /Home/Create [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(FormCollection collection) { try { // TODO: Add insert logic here return RedirectToAction("Index"); } catch { return View(); } } // // GET: /Home/Edit/5 public ActionResult Edit(int id) { return View(); } // // POST: /Home/Edit/5 [AcceptVerbs(HttpVerbs.Post)] public ActionResult Edit(int id, FormCollection collection) { try { // TODO: Add update logic here return RedirectToAction("Index"); } catch { return View(); } } } }
联系人列表
为了显示数据库中Contacts表中的联系人记录,我们需要建立一个Index() action和一个Index view。
Home controller中已经准备好了Index() action。我们需要修改这个方法至如下代码所示:
private ContactManagerDBEntities _entities = new ContactManagerDBEntities(); // // GET: /Home/ public ActionResult Index() { return View(_entities.ContactSet.ToList()); }
上面的代码中,Home controller类包含一个叫做_entities的私有字段,它用来与数据库通信及描述数据模型中的实体。
Index()方法返回一个展现数据库中Contacts表中所有联系人的view。表达式_entities.ContactSet.ToList()返回一个联系人的泛型集合。
现在我们已经建立好了Index controller,接下来我们需要建立Index view。在建立Index view之前,请编译你的应用程序(Ctrl+Shift+B)。为了确保所有的模型类都出现在Add Viwe对话框的列表中,你应该在每次添加一个view之前编译你的项目。
要建立Index view,请右击Index()方法,跟着选择Add View(图12)
图12
在Add View对话框中,选定复选框Create a strongly-typed view。选定View data class为ContactManager.Models.Contact及View content为List。通过这些选项IDE将生成一个显示联系人记录列表的view。
图13
点击Add按钮后,Index view就被创建完毕了。注意页首的<%@ Page %>指令,它指定Index view继承自ViewPage<IEnumerable<ContactManager.Models.Contact>>类。也就是说,view中的Model类即可表现联系人实体列表。
Index view的主体中包括一个foreach循环,它用来从Model中枚举出每一个联系人信息。每一个Contact类中属性的值都被显示在Html表格中。
由于我们并未创建一个Details view,所以需要对此作出一点修改。我们可以删除Details链接,从找到如下代码中所示的内容,并删除它:
<%= Html.ActionLink("Details", "Details", new { id=item.Id })%>
最终view的代码如下:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<ContactManager.Models.Contact>>" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <h2> Index</h2> <table> <tr> <th> </th> <th> Id </th> <th> FirstName </th> <th> LastName </th> <th> Phone </th> <th> Email </th> </tr> <% foreach (var item in Model) { %> <tr> <td> <%= Html.ActionLink("Edit", "Edit", new { id=item.Id }) %> | </td> <td> <%= Html.Encode(item.Id) %> </td> <td> <%= Html.Encode(item.FirstName) %> </td> <td> <%= Html.Encode(item.LastName) %> </td> <td> <%= Html.Encode(item.Phone) %> </td> <td> <%= Html.Encode(item.Email) %> </td> </tr> <% } %> </table> <p> <%= Html.ActionLink("Create New", "Create") %> </p> </asp:Content>
完成对Index view的修改后,你便可以按F5运行Contact Manager应用程序了。
当你第一次运行应用程序的时候会出现如图14所示的对话框。选择修改Web.config文件以启用调试,然后点击确定按钮。
图14
运行结果一目了然(图15),Index view返回默认的action用以显示Contacts表中的数据列表。
图15
注意,Index view底部有一个Create New的链接标签。在下一节中你将学习到如何创建新的联系人。
创建新联系人
为了使用户可以建立新的联系人,我们需要向Home controller添加两个Create() action。其中一个Create() action返回创建新联系人的HTML表单,另外一个则用来向数据库写入新联系人的数据。
两个Create()方法代码如下
// // GET: /Home/Create public ActionResult Create() { return View(); } // // POST: /Home/Create [AcceptVerbs(HttpVerbs.Post)] public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate) { if (!ModelState.IsValid) { return View(); } else { try { _entities.AddToContactSet(contactToCreate); _entities.SaveChanges(); return RedirectToAction("Index"); } catch { return View(); } } }
第一个Create()方法会被HTTP GET请求调用,而第二个Create()方法则会被HTTP POST请求调用。也就是说,第二个Create()方法只会在HTML表单被提交时被调用,而第一个Create()方法只是简单返回一个用于创建新联系人的Html表单。第二个Create()方法更有趣些:它将新的联系人添加到数据库中。
这里需要注意的是,我们已经将第二个Create()方法修改为接受一个Contact类的实例。HTML表单中提交的值都被绑定到这个Contact类。这一切都是ASP.NET MVC framework自动完成的。每一个表单对象都指向一个Contact参数。
另外还需注意的是这里的Contact参数被[Bind]特性所修饰。这里[Bind]特性用来将Id属性从绑定中排除。因为Id属性是一个自动标识,我们并不希望设置这个属性。
在Create()方法中,AEF用来向数据库中插入新的联系人数据。新联系人被添加在已存在的Contacts集中,而SaveChanges()方法调用时则将这些变更反映到数据库中。
你可以通过右击任意一个Create()方法然后选择菜单选项中的Add View,从而为添加新联系人建立一个HTML表单(图16)
图16
在Add View对话框中,为view content(图17)选择ContactManager.Models.Contact类以及Create选项。点击Add后,一个Create view就被自动的建立出来了。
图17
Create view生成的表单中的对象对应每一个Contact类的属性。代码如下:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <h2>Create</h2> <%= Html.ValidationSummary("Create was unsuccessful. Please correct the errors and try again.") %> <% using (Html.BeginForm()) {%> <fieldset> <legend>Fields</legend> <p> <label for="FirstName">FirstName:</label> <%= Html.TextBox("FirstName") %> <%= Html.ValidationMessage("FirstName", "*") %> </p> <p> <label for="LastName">LastName:</label> <%= Html.TextBox("LastName") %> <%= Html.ValidationMessage("LastName", "*") %> </p> <p> <label for="Phone">Phone:</label> <%= Html.TextBox("Phone") %> <%= Html.ValidationMessage("Phone", "*") %> </p> <p> <label for="Email">Email:</label> <%= Html.TextBox("Email") %> <%= Html.ValidationMessage("Email", "*") %> </p> <p> <input type="submit" value="Create" /> </p> </fieldset> <% } %> <div> <%=Html.ActionLink("Back to List", "Index") %> </div> </asp:Content>
完成Create()方法的修改及Create view的添加后,你就可以运行Contact Manager应用程序并创建新的联系人了。方法自然是点击Index view中的Create New这个链接。它将页面导航至如图18所示的页面
图18
编辑联系人信息
为项目添加生成编辑联系人记录的方法与添加创建联系人记录的方法十分相似。首先,我们需要为Home controller类添加两个Edit()方法,如下:
// // GET: /Home/Edit/5 public ActionResult Edit(int id) { var contractToEdit = _entities.ContactSet.Where(c => c.Id == id).FirstOrDefault(); return View(contractToEdit); } // // POST: /Home/Edit/5 [AcceptVerbs(HttpVerbs.Post)] public ActionResult Edit(Contact contactToEdit) { if (!ModelState.IsValid) return View(); try { var originalContact = _entities.ContactSet.Where(c => c.Id == contactToEdit.Id).FirstOrDefault(); _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit); _entities.SaveChanges(); return RedirectToAction("Index"); } catch { return View(); } }
第一个Edit()方法被HTTP GET操作调用,参数Id为代表将要编辑的联系人记录的特定Id。AEF使用Id来匹配数据库中的特定记录并将其取出,进而返回一个编辑记录的HTML表单。
第二个Edit()方法用于与数据库通信更新记录。这个方法接受一个Contact类的实例作为参数。ASP.NET MVC framework会自动将类中的属性及值绑定到表单的对象上。与上面建立Create()放法所不同的是,这里并没有使用[Bind]特性进行标记,因为我们这里需要Id属性的值。
AEF使用更新后的Contact实体更新数据库。首先,源Contact必须从数据库中取出,然后AEF的ApplyPropertyChanges()方法被调用从而记录所有改动。最后AEF的SaveChanges()方法将实际的更新结果反映到数据库。
你可以通过右击任意一个Edit()方法然后选择菜单中的Add View选项。在Add View对话框中选择ContactManager.Models.Contact及Edit view content(图19)
图19
点击Add按钮后,一个新的Edit view就被自动创建出来了。HTML表单中的表单对象也是根据Contact类中的每个属性生成的。具体代码如下:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <h2>Edit</h2> <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %> <% using (Html.BeginForm()) {%> <fieldset> <legend>Fields</legend> <p> <label for="Id">Id:</label> <%= Html.TextBox("Id", Model.Id) %> <%= Html.ValidationMessage("Id", "*") %> </p> <p> <label for="FirstName">FirstName:</label> <%= Html.TextBox("FirstName", Model.FirstName) %> <%= Html.ValidationMessage("FirstName", "*") %> </p> <p> <label for="LastName">LastName:</label> <%= Html.TextBox("LastName", Model.LastName) %> <%= Html.ValidationMessage("LastName", "*") %> </p> <p> <label for="Phone">Phone:</label> <%= Html.TextBox("Phone", Model.Phone) %> <%= Html.ValidationMessage("Phone", "*") %> </p> <p> <label for="Email">Email:</label> <%= Html.TextBox("Email", Model.Email) %> <%= Html.ValidationMessage("Email", "*") %> </p> <p> <input type="submit" value="Save" /> </p> </fieldset> <% } %> <div> <%=Html.ActionLink("Back to List", "Index") %> </div> </asp:Content>
删除联系人
如果你希望为应用程序增加删除联系人的功能,则你需要在Home controller类中添加两个Delete() action。第一个Delete() action显示一个确认删除表单,另外一个则执行真正的删除过程。
PS:我们将在迭代7中修改Contact Manager,为其添加基于Ajax的删除功能。
两个Delete()方法的具体代码如下:
// // GET: /Home/Delete/5 public ActionResult Delete(int id) { var contactToDelete = _entities.ContactSet.Where(c => c.Id == id).FirstOrDefault(); return View(contactToDelete); } // // POST: /Home/Delete/5 [AcceptVerbs(HttpVerbs.Post)] public ActionResult Delete(Contact contactToDelete) { try { var originalContact = _entities.ContactSet.Where(c => c.Id == contactToDelete.Id).FirstOrDefault(); _entities.DeleteObject(originalContact); _entities.SaveChanges(); return RedirectToAction("Index"); } catch { return View(); } }
第一个Delete()方法返回一个确认从数据库中删除一条联系人记录的的表单(图20)。第二个Delete()方法则用来切实的从数据库中删除记录。源联系人记录被从数据库中取出后,AEF的DeleteObject()及SaveChanges()方法分别被调用,进而从数据库中删除该联系人。
图20
并且,我们需要更改Index view,为其添加一个删除联系人记录的链接(图21)。你需要将如下代码写入与Edit链接相同的那个单元格。
<%= Html.ActionLink("Delete","Delete",new { id=item.Id }) %>
图21
接着,我们需要添加确认删除页的view。右击Home controller类中的任意一个Delete()方法,选择弹出菜单中的Add View选项。此时弹出Add View对话框(图22)
需要注意的是,与建立List,Create及Edit的对应view不同的是,这个对话框并不包含建立Delete view的选项,我们需要在这里选择ContactManager.Models.Contact 及Empty view content。
图22
为了让此view作为确认某个特定的联系人的确认表单,我们需要修改这个view至如下代码所示。
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ContactManager.Models.Contact>" %> <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server"> <h2> Delete</h2> <p> Are you sure that you want to delete the entry for <%= Model.FirstName %> <%= Model.LastName %>? </p> <% using (Html.BeginForm(new { Id = Model.Id })) { %> <p> <input type="submit" value="Delete" /> </p> <% } %> <div> <%=Html.ActionLink("Back to List", "Index") %> </div> </asp:Content>
改变默认Controller的名称
我们用来处理联系人信息的controller类的名称叫做HomeController,这很有些词不达意,难道就不能将其命名为ContactConstroller吗?
这个问题好说。首先我们需要重构Home controller的名称。在VS的代码编辑器中打开HomeController类,右击类名选择弹出菜单中的重构,重命名(图23)。
图23
然后将弹出重命名对话框(图24)
图24
如果你重命名了controller类,VS会同时更新View文件夹下子文件夹的名称。即\Views\Home文件夹被重命名为\Views\Contact。
做好了如上重构后,你的应用程序中的Home controller将不复存在。当应用程度运行时你将会看到图25所示的错误页面。
图25
我们需要在Global.asax文件中将默认route从Home controller修改成Contact controller。打开Global.asax文件并修改default route节至如下代码所示:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace ContactManager { public class MvcApplication : System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Contact", action = "Index", id = "" } // Parameter defaults ); } protected void Application_Start() { RegisterRoutes(RouteTable.Routes); } } }
总结
在本次(也是第一次)迭代中,我们力求简单快速的建立了Contact Manager应用程序的基本结构。在这个过程中,我们充分的利用了VS强大功能自动的生成controller和view的基本代码。同时也体会到使用AEF自动创建数据模型的方便与快捷。
现在,我们的Contact Manager应用程序已经具有列表、建立、编辑以及删除联系人的功能,也就是说我们已经可以驾驭所有的数据库驱动web应用程序的基本数据操作。
遗憾的是,我们的应用程序还存在一些问题。我不得不承认,我们的Contact Manager应用程序并不是非常具有吸引力--它需要一些设计和修饰。在下一次迭代中,我们将一起通过修改默认view的母板页的CSS体验提升应用程序表现的过程。
再则,我们还未实现任何的表单验证功能,比如现在并没有任何规则限制用户提交不完全的表单内容。以后我们会逐渐添加对电话号码以及电子邮件地址的验证。我们将在迭代3中处理这些问题。
最后,当然也是最重要的:当前的Contact Manager 应用程序的很难修改及维护。如数据库的存储逻辑被散布在controller action中。这就意味着我们想要修改数据存储相关的功能就必须修改controller。在以后的迭代过程中,我们将使用一些设计模式来改善这种情况,似的我们的Contact Manager更具弹性、更易修改。