原文出处:Chapter 2 - Building a Simple ASP.NET MVC Application
在上一章,我们讨论了ASP.NET MVC 框架的崇高目标,在本章中,我们完全忽略它们,我们以最简单的可能方式来构建一个数据库驱动的ASP.NET MVC应用程序。我们忽略设计原则和模式,甚至连简单的单元测试也不会创建。本章的目标就是说明构建ASP.NET MVC应用程序的基本机制。
在本章中,我们构建一个简单的玩具商店程序,我们的程序可以现实一个玩具的列表,并且可以新建玩具。换言之,它可以说明如何构建应用程序,来对数据库执行基本的操作。
*** Begin Note ***
本书的第三部分专门用来讲述如何通过“正确”的方式来构建ASP.NET MVC 程序,我们会利用软件设计原则和模式以及测试驱动来构建一个论坛应用程序。
*** End Note ***
Starting with a Blank Slate
让我们开始创建一个新的ASP.NET MVC 程序,并且移除所有的示例文件。请按照以下步骤新建一个ASP.NET
MVC 应用程序工程:
1. 运行Visual Studio 2008。
2. 选择File->New Project。
3. 在New Project对话框中,选择你喜欢的编程语言和ASP.NET
MVC Web 应用程序模板(参见图1)。
4. 为你的新工程命名为ToyStore,点击OK按钮。
图1
– 创建新的ASP.NET MVC工程
当你创建一个新的ASP.NET MVC 工程,会自动出现创建单元测试工程的对话框(参见图2),当被询问是否创建一个单元测试工程时,选择Yes,创建一个测试工程 (通常,你应该总是选择Yes,因为当你创建好ASP.NET MVC 工程后,在条件单元测试工程,会是一个痛苦的过程)。
图2
– 创建单元测试工程对话框
*** Begin Note ***
当你使用Microsoft Visual Web Developer 创建ASP.NET MVC
工程时,单元测试工程对话框是不会出现的,因为Visual Web Developer 不支持测试工程类型。
*** End Note ***
就像我们在上一章中讨论的那样,当创建一个ASP.NET MVC 应用程序时,你会默认得到几个示例文件。这些文件对我我们构建一个新的应用程序来说,是一个阻碍。请在ASP.NET MVC 工程中删除以下文件:
[C#]
\Controllers\HomeController.cs
\Views\Home\About.aspx
\Views\Home\Index.aspx
[VB]
\Controllers\HomeController.vb
\Views\Home\About.aspx
\Views\Home\Index.aspx
Delete the following file from your Test project.
[C#]
\Controllers\HomeControllerTest.cs
[VB]
\Controllers\HomeControllerTest.vb
*** Begin Tip ***
如果你想总是以一个空的ASP.NET MVC 工程开始,那么在删除示例文件后,你可以创建一个新的Visual
Studio工程模板,可以通过选择File->Export Template来创建一个新的工程模板。
*** End Tip ***
Creating the Database
我们需要创建一个数据库和数据库表,来保存我们玩具商店中的玩具列表,ASP.NET MVC 框架和任何当前主流的数据库兼容,包括Oracle 11g、MySQL和Microsoft SQL Server。
在本书中,我们使用Microsoft SQL Server Express作为我们的数据库。Microsoft
SQL Server Express是Microsoft SQL Server的一个免费版本,它包含了Microsoft SQL Server完整版本的所有基本功能(使用的数据库引擎是一样的)。
*** Begin Note ***
当你安装Visual Studio或者Visual Web Developer时,可以选择安装Microsoft SQL Server Express(这是一个安装选项)。你可以通过访问以下站点对Microsoft SQL Server Express进行网络安装:http://www.asp.net/downloads/。
*** End Note ***
请按照以下步骤在Visual Studio中创建一个新的数据库:
1. 在解决方案窗口的App_Data文件上单击鼠标右键,选择Add->New Items。
2. 在Add New Item对话框中,选择SQL Server Database模板 (参见图3)。
3. 为新的数据库命名为ToyStoreDB。
4. 点击Add按钮。
图3–
添加一个新的SQL Server数据库
在创建完数据库后,你需要创建一个Table,来保存玩具列表。请按照以下步骤创建Products表:
1. 在App_Data文件夹中双击ToyStoreDB.mdf文件,打开连接ToyStoreDB数据库的Server Explorer 窗口。
2. 在Tables文件夹上点击鼠标右键,选择Add New Table。
3. 在Table设计器中输入表1所列的所有列(参见图4)。
4. 通过以下方式将Id列设置为Identify列:在Column Properties中展开Identify Specification节点,然后将
Is Identity属性设置为Yes。
5. 通过以下方式将Id列设置为主键列:在Table设计器中选择该列,然后点击Set Primary Key 按钮。
6. 通过点击Save按钮保存Table。Save the new table by clicking the Save button (一个带有过时的软盘图标的按钮)。
7. 在Choose Name 对话框中,输入名称 Products。
表
1 – Products表中的列
列名 |
数据类型 |
是否允许为空 |
Id |
Int |
False |
Name |
Nvarchar(100) |
False |
Description |
nvarchar(MAX) |
False |
Price |
money |
False |
图
4 – 创建Products表
*** Begin Note ***
在Visual
Web Developer中,服务器浏览器被称为数据库浏览器。
*** End Note ***
在完成Products表的创建后,你应该向其中插入一些数据。在服务器窗口中右键单击Products表,选择Show Table Data,输入两三条产品信息(参见图5)。
图
5 – 在Products表中输入示例数据
Creating the Model
在ASP.NET
MVC程序中,我们需要创建模型类来表示数据库中的表,创建模型类最容易的方法就是利用ORM,自动的从数据库中生成模型类。
你可以将你喜欢的ORM和ASP.NET MVC 框架合在一起使用,ASP.NET MVC 框剪并没有和任何ORM绑定在一起。例如,ASP.NET MVC和Microsoft LINQ to SQL、NHibernate以及Microsoft Entity框架都兼容。
在本书中,我们使用Microsoft Entity框架来生成我们的模型类,我们关注Microsoft
Entity框架是因为这个框架啊是Microsoft在数据访问解决方案方面推荐的框架。
*** Begin Note ***
为了使用Microsoft Entity框架,你需要安装.NET Framework 3.5 SP1。
*** End Note ***
请按照以下步骤生成数据模型类:
1. 在解决方案窗口的Models文件夹上右键点击鼠标,选择 Add->New Item。
2. 选择Data种类和ADO.NET
Entity Data Model 模板 (参见图6)。
3. 将数据模型命名为ToyStoreDataModel.edmx,点击Add按钮。
图6
– 添加ADO.NET Entity数据模型类
在完成以上步骤后,会运行Entity模型数据向导。完成以下这些向导步骤:
1. 在Choose Model Contents步骤, 选择Generate from database。
2. 在Choose Your Data Connection步骤, 选择ToyStoreDB.mdf数据连接,将实体连接命名为ToyStoreDBEntities (参见图7)。
3. 在Choose Your Database Objects步骤, 选择Products表,并输入模型类的命名空间 (参见图8)。
4. 点击Finish按钮,完成向导。
图
7 – 选择数据连接
Figure 8 – 输入模型类的命名空间
在完成Entity数据模型向导后,可以看到Entity设计器,设计器中只带了一个名为Products的实体(参见图9)。Entity框架已经生成了一个名为Products的类,用来表示数据库中的Products表。
大部分情况下,你会想重命名通过Entity框架生成的模型类,Entity框架简单的将实体的名字命名为数据苦衷对应的表名,因为Products类代表一个特定的产品,你可能想将其名称更改为Products。 (单数而不是复数)。
在Entity设计器中右键单击Products实体,选择Rename,输入名称Product。
图9
– Entity设计器
至此,我们已经成功的创建了模型类。在ASP.NET MVC 程序中,我们可以使用这些类来代表ToyStoreDB数据库中的表。
*** Begin Note ***
在以后,你可以通过双击Models文件夹中的ToyStoreDataModel.edmx 来打开Entity设计器。
*** End Note ***
Creating the Controller
在ASP.NET
MVC 程序中,控制器控制应用程序执行的流程。被默认出发的控制器名为Home,我们可以通过以下步骤创建Home控制器:
1. 在Controller文件夹上右键点击鼠标,选择Add Controller。
2. 在Add Controller 对话框中, 输入控制器的名称: HomeController, 然后勾选上Add action
methods for Create, Update, and Details scenarios (参见图10)。
3. 点击Add按钮,创建新的控制器。
图10
– 添加新的控制器
Home控制器的内容如列表1所示。
列表1 – Controllers\HomeController.cs [C#]
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
namespace ToyStore.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();
}
}
}
}
列表 1 – Controllers\HomeController.vb [VB]
Inherits System.Web.Mvc.Controller
'
' GET: /Home/
Function Index() As ActionResult
Return View()
End Function
'
' GET: /Home/Details/5
Function Details(ByVal id As Integer) As ActionResult
Return View()
End Function
'
' GET: /Home/Create
Function Create() As ActionResult
Return View()
End Function
'
' POST: /Home/Create
<AcceptVerbs(HttpVerbs.Post)> _
Function Create(ByVal collection As FormCollection) As ActionResult
Try
' TODO: Add insert logic here
Return RedirectToAction("Index")
Catch
Return View()
End Try
End Function
'
' GET: /Home/Edit/5
Function Edit(ByVal id As Integer) As ActionResult
Return View()
End Function
'
' POST: /Home/Edit/5
<AcceptVerbs(HttpVerbs.Post)> _
Function Edit(ByVal id As Integer, ByVal collection As FormCollection) As ActionResult
Try
' TODO: Add update logic here
Return RedirectToAction("Index")
Catch
Return View()
End Try
End Function
End Class
因为我们在新建Home控制器时,勾选了
generate Create, Update, and Details methods, 所以列表1所示的内容中包括了这些action。特别的,Home控制器对外暴露了以下action:
· Index() – 这是控制器默认的action,一般的,这个action用来显示一些条目的列表。
· Details(id) – 这个action显示某一个特定条目的详细内容。
· Create() – 这个action显示一个新建条目的窗体。
· Create(collection) – 这个action将一个新的条目插入到数据库中。
· Edit(id) – 这个action显示一个用于编辑现有条目的窗体。
· Edit(id, collection) – 这个action将现有条目更新到数据库中。
目前,Home控制器中只包含了这些action的声明,我们继续使用由Entity框架生成的数据模型类来实现Index() 和 Create()。更新后的Home控制器内容如列表2所示。
列表2 – Controllers\HomeController.cs [C#]
using System.Web.Mvc;
using ToyStore.Models;
namespace ToyStore.Controllers
{
public class HomeController : Controller
{
private ToyStoreDBEntities _dataModel = new ToyStoreDBEntities();
//
// GET: /Home/
public ActionResult Index()
{
return View(_dataModel.ProductSet.ToList());
}
//
// GET: /Home/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Home/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="Id")]Product productToCreate)
{
if (!ModelState.IsValid)
return View();
try
{
_dataModel.AddToProductSet(productToCreate);
_dataModel.SaveChanges();
return RedirectToAction("Index");
}
catch
{
return View();
}
}
}
}
列表 2 – Controllers\HomeController.vb [VB]
Inherits System.Web.Mvc.Controller
Private _dataModel As New ToyStoreDBEntities()
'
' GET: /Home/
Function Index() As ActionResult
Return View(_dataModel.ProductSet.ToList())
End Function
'
' GET: /Home/Create
Function Create() As ActionResult
Return View()
End Function
'
' POST: /Home/Create
<AcceptVerbs(HttpVerbs.Post)> _
Function Create(<Bind(Exclude:="Id")> ByVal productToCreate As Product) As ActionResult
If Not ModelState.IsValid Then
Return View()
End If
Try
_dataModel.AddToProductSet(productToCreate)
_dataModel.SaveChanges()
Return RedirectToAction("Index")
Catch
Return View()
End Try
End Function
End Class
请注意在列表2所示内容的顶部有一个私有字段,类型为DBStoreEntities,名为_dataModel。DBStoreEntities类是由Entity模型数据向导生成的一个类,用于和数据库进行通信。
Index()action已经被改为返回一个产品的列表。_dataModel.ProductSet.ToList() 语句返回从Products表中返回的一个products的列表。
内容中包含了两个Create() action,第一个Create() action用于显示一个创建新产品的窗体,这个窗体被提交给第二个Create() action,这个action向数据库中插入新产品。
请注意第二个Create() action已经被改为接收一个Products参数。Product类也是由Entity模型数据向导生成的,它里面的每一个属性对应数据库Products表中的一列。
最后,Create() action调用以下方法来向数据库中添加新的产品信息:
[C#]
_dataModel.AddToProductSet(productToCreate);
_dataModel.SaveChanges();
[VB]
_dataModel.AddToProductSet(productToCreate)
_dataModel.SaveChanges()
现在Home控制器中已经包含了所有必要的数据库逻辑。我们可以使用这个控制器返回一个产品的集合,并可以新建一个产品。
请注意Index()和Create()方法都返回一个View对象,接下来,也是最后一步,我们来创建这些View。
Creating the Views
一个MVC视图中包含了所有的HTML标签和用于生成HTML页的视图逻辑。这些由ASP.NET MVC程序暴露出来的视图集合就是程序的可以看到的外表。
*** Begin Note ***
视图并不一定是HTML。例如,你可以创建一个Sliverlight视图。
*** End Note ***
我们的简单程序需要两个视图:Index视图和Create视图。我们使用Index视图来现实所有的产品列表,使用Create视图来显示创建新产品的窗体。
Adding the Index View
让我们先创建Index视图,请按照以下步骤创建:
1. 选择菜单Build->Build Solution来编译应用程序。
2. 在代码编辑器中右键单击Index(),选择Add View (参见图11).
3. 在Add View 对话框中,选择 Create a strongly-typed view。
4. 在Add View对话框中, 从View data class 的下拉列表中,选择ToyStore.Models.Product类。
5. 在Add View 对话框中, 从View
Content的下拉列表中,选择List。
6. 点击Add按钮,将新的视图添加到你的工程中(参见图12)。
图
11 – 添加一个视图
图
12 – 添加视图对话框
*** Begin Note ***
为了在View data class下拉列表中显示类的名字,你需要在添加视图之前重新编译程序。如果程序编译不通过,那么这个列表会显示为空。
*** End Note ***
视图会被添加到Views文件夹,视图遵循一个特别的命名约定。通过Home控制器的Index()返回的视图的存储位置如下:
\Views\Home\Index.aspx
一般,视图遵循以下的命名约定:
\Views\Controller Name\Action Name.aspx
Index视图的内容如列表3所示,这个视图循环了所有的产品,然后将其显示到HTML中的表格中(参见图13)。
列表3 – Views\Home\Index.aspx [C#]
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<title>Index</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<table>
<tr>
<th></th>
<th>
Id
</th>
<th>
Name
</th>
<th>
Description
</th>
<th>
Price
</th>
</tr>
<% foreach (var item in Model) { %>
<tr>
<td>
<%= Html.ActionLink("Edit", "Edit", new { /* id=item.PrimaryKey */ }) %> |
<%= Html.ActionLink("Details", "Details", new { /* id=item.PrimaryKey */ })%>
</td>
<td>
<%= Html.Encode(item.Id) %>
</td>
<td>
<%= Html.Encode(item.Name) %>
</td>
<td>
<%= Html.Encode(item.Description) %>
</td>
<td>
<%= Html.Encode(item.Price) %>
</td>
</tr>
<% } %>
</table>
<p>
<%= Html.ActionLink("Create New", "Create") %>
</p>
</asp:Content>
列表3 – Views\Home\Index.aspx [VB]
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<title>Index</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Index</h2>
<p>
<%=Html.ActionLink("Create New", "Create")%>
</p>
<table>
<tr>
<th></th>
<th>
Id
</th>
<th>
Name
</th>
<th>
Description
</th>
<th>
Price
</th>
</tr>
<% For Each item In Model%>
<tr>
<td>
<%--<%=Html.ActionLink("Edit", "Edit", New With {.id = item.PrimaryKey})%> |
<%=Html.ActionLink("Details", "Details", New With {.id = item.PrimaryKey})%>--%>
</td>
<td>
<%=Html.Encode(item.Id)%>
</td>
<td>
<%=Html.Encode(item.Name)%>
</td>
<td>
<%=Html.Encode(item.Description)%>
</td>
<td>
<%=Html.Encode(item.Price)%>
</td>
</tr>
<% Next%>
</table>
</asp:Content>
图
13 – The Index view
请注意,在Index视图的底部,有一个链接名为Create New,我们接下来添加Create视图。
Adding the Create View
Create视图显示一个用于添加新产品的窗体,我们可以按照类似的步骤添加Create视图:
1. 在代码编辑器窗口中右键单击第一个Create(),选择Add View。
2. 在Add View 对话框中, 选择Create a strongly-typed view。
3. 在Add View 对话框中, 在View data class的下拉列表中, 选择ToyStore.Models.Product类。
4. 在Add View 对话框中, 在View Content的下拉列表中,选择Create。
5. 单击Add按钮,将视图添加到工程中(参见图14)。
图
14 – 添加Create视图
Create视图被添加到项目的以下位置:
\Views\Home\Create.aspx
Create视图的内容如列表4所示:
列表 4 – Views\Home\Create.aspx [C#]
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<title>Create</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Id">Id:</label>
<%= Html.TextBox("Id") %>
<%= Html.ValidationMessage("Id", "*") %>
</p>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Description">Description:</label>
<%= Html.TextBox("Description") %>
<%= Html.ValidationMessage("Description", "*") %>
</p>
<p>
<label for="Price">Price:</label>
<%= Html.TextBox("Price") %>
<%= Html.ValidationMessage("Price", "*") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% } %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
列表 4 – Views\Home\Create.aspx [VB]
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="server">
<title>Create</title>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>Create</h2>
<%= Html.ValidationSummary() %>
<% Using Html.BeginForm()%>
<fieldset>
<legend>Fields</legend>
<p>
<label for="Id">Id:</label>
<%= Html.TextBox("Id") %>
<%= Html.ValidationMessage("Id", "*") %>
</p>
<p>
<label for="Name">Name:</label>
<%= Html.TextBox("Name") %>
<%= Html.ValidationMessage("Name", "*") %>
</p>
<p>
<label for="Description">Description:</label>
<%= Html.TextBox("Description") %>
<%= Html.ValidationMessage("Description", "*") %>
</p>
<p>
<label for="Price">Price:</label>
<%= Html.TextBox("Price") %>
<%= Html.ValidationMessage("Price", "*") %>
</p>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
<% End Using %>
<div>
<%=Html.ActionLink("Back to List", "Index") %>
</div>
</asp:Content>
Create视图显示一个用于创建新产品的HTML窗体(参见图15)。Add视图对话框会生成一个针对Product类的每个属性的HTML窗体。如果你输入完HTML窗体,然后提交,一条新的产品会被插入到数据库中。
图
15 – Create视图
Summary
在本章中,我们使用ASP.NET MVC 框架构建了一个简单的数据库驱动的web应用程序,我们创建了模型、视图和控制器。
首先,我们创建数据库和数据库模型我们使用Microsoft SQL Server Express 作为数据库,利用Microsoft
Entity 框架生成模型类。
接下来,我们创建了Home控制器。我们使用Visual Studio自动生成了Home控制器中的action,天价了一些数据访问逻辑代码来和数据库进行交互。
最后,我们创建了两个视图。我们创建了Index视图来在HTML表格中现实所有的产品列表;创建了Create视图来显示一个用于添加新产品的HTML窗体。