使用ASP.NET MVC 3、Razor和Entity Framework Code First技术开发Web应用程序 – Part 2
上文《使用ASP.NET MVC 3、Razor和Entity Framework Code First技术开发Web应用程序 – Part 1》 中讨论了如何应用ASP.NET MVC 3 和 EF Code First技术开发Web应用程序,其中包括了Generic Repository和Unit of Work模式,对Category 业务实体实现了简单的CRUD操作。本文,我们进一步演示业务实体对象关系、服务层(Service Layer)和View Model,并完成范例程序的剩余工作。本文关注的Expense 实体对象和Category 实体有关联关系。范例程序的源代码下载:http://efmvc.codeplex.com
本范例程序的运行界面:
![]()
业务实体模型 – Domain Model
Category实体类
|
1
2
3
4
5
6
7
8
9
|
public class Category{public int CategoryId { get; set; }[Required(ErrorMessage = "Name Required")][StringLength(25, ErrorMessage = "Must be less than 25 characters")]public string Name { get; set;}public string Description { get; set; }public virtual ICollection<Expense> Expenses { get; set; }} |
Expense实体类
|
1
2
3
4
5
6
7
8
9
|
public class Expense{public int ExpenseId { get; set; }public string Transaction { get; set; }public DateTime Date { get; set; }public double Amount { get; set; }public int CategoryId { get; set; }public virtual Category Category { get; set; }} |
一个Category实体对象包含一个Expense实体对象集合,且每一个Expense实体对象移动属于一个Category实体对象。
Expense 实体相关的Repository类
下面创建负责Expense实体CRUD操作的Repository类。
|
1
2
3
4
5
6
7
8
9
10
|
public class ExpenseRepository : RepositoryBase<Expense>, IExpenseRepository{public ExpenseRepository(IDatabaseFactory databaseFactory): base(databaseFactory){}}public interface IExpenseRepository : IRepository<Expense>{} |
服务层-Service Layer
服务层定义了应用程序的边界和一系列可用的方法,它封装了应用程序的业务逻辑、控制事务和协调方法中的响应。Controller 类应该轻量级的,不要将过多业务逻辑放置在其中。我们将Service Layer作为业务逻辑层(Business Logic Layer),封装了应用程序的逻辑。
创建Expense相关的Service 类
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public interface IExpenseService{IEnumerable<Expense> GetExpenses(DateTime startDate, DateTime ednDate);Expense GetExpense(int id);void CreateExpense(Expense expense);void DeleteExpense(int id);void SaveExpense();}public class ExpenseService : IExpenseService{private readonly IExpenseRepository expenseRepository;private readonly IUnitOfWork unitOfWork;public ExpenseService(IExpenseRepository expenseRepository, IUnitOfWork unitOfWork){this.expenseRepository = expenseRepository;this.unitOfWork = unitOfWork;}public IEnumerable<Expense> GetExpenses(DateTime startDate, DateTime endDate){var expenses = expenseRepository.GetMany(exp => exp.Date >= startDate && exp.Date <= endDate);return expenses;}public void CreateExpense(Expense expense){expenseRepository.Add(expense);unitOfWork.Commit();} |
Expense 事务相关的View Model
在ASP.NET MVC 开发过程中,经常需要为View 视图定义一些Model。之前定义的Model主要是业务Model,一般和数据库相关。而View Model对象一般是为了View显示的需要。Expense 业务实体和Category 存在关联,在我们创建一个新的Expense对象时,需要指定Expense 所属于的Category。因此,Expense 事务的 UI 界面将包含表示Expense 实体的字段内容,同时还包含CategoryId,表示Expense所存在的Category。下面创建Expense 事务所需要的View Model。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class ExpenseViewModel{public int ExpenseId { get; set; }[Required(ErrorMessage = "Category Required")]public int CategoryId { get; set; }[Required(ErrorMessage = "Transaction Required")]public string Transaction { get; set; }[Required(ErrorMessage = "Date Required")]public DateTime Date { get; set; }[Required(ErrorMessage = "Amount Required")]public double Amount { get; set; }public IEnumerable<SelectListItem> Category { get; set; }} |
ExpenseViewModel用于在View模板中显示记录,且包含所有的验证逻辑。它的属性分别映射到对应的Expense 实体属性,另外一个Category属性用来在下拉列表框中绑定列表项。
创建Expense事务
下面在ExpenseController类创建Create Action方法。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public ActionResult Create(){var expenseModel = new ExpenseViewModel();var categories = categoryService.GetCategories();expenseModel.Category = categories.ToSelectListItems(-1);expenseModel.Date = DateTime.Today;return View(expenseModel);}[HttpPost]public ActionResult Create(ExpenseViewModel expenseViewModel){if (!ModelState.IsValid){var categories = categoryService.GetCategories();expenseViewModel.Category = categories.ToSelectListItems(expenseViewModel.CategoryId);return View("Save", expenseViewModel);}Expense expense=new Expense();ModelCopier.CopyModel(expenseViewModel,expense);expenseService.CreateExpense(expense);return RedirectToAction("Index");} |
在负责HttpGet 请求的Create Action方法中,创建了一个视图模型的ExpenseViewModel对象实例,并包含了用于下拉列表框显示的Category信息,最后返回 Model对象给View模板。ToSelectListItems扩展方法的代码如下所示:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public static IEnumerable<SelectListItem> ToSelectListItems(this IEnumerable<Category> categories, int selectedId){returncategories.OrderBy(category => category.Name).Select(category =>new SelectListItem{Selected = (category.CategoryId == selectedId),Text = category.Name,Value = category.CategoryId.ToString()});} |
在负责HttpPost的Create Action方法中,视图模型ExpenseViewModel对象将映射form表单提交的内容。我们需要创建一个Expense对象实例,用来持久化 数据。因此,需要从ExpenseViewModel对象将值复制到Expense对象。ASP.NET MVC futures提供了一个静态方法 ModelCopier,可用来在Model对象之间复制属性值。ModelCopier类提供了2个静态方法:CopyCollection 和CopyModel。CopyCollection 方法用来在2个集合对象之间复制值,CopyModel方法则用于在2个Model对象之间复制值。上面使用了ModelCopier的 CopyModel方法从expenseViewModel对象复制属性值到expense对象。最后,调用ExpenseService类的 CreateExpense方法持久化新的expense事务数据。
Expense事务列表
下面根据日期范围显示Expense事务列表,创建对应的Action方法显示符合日期范围的expense事务记录。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public ActionResult Index(DateTime? startDate, DateTime? endDate){//If date is not passed, take current month's first and last dteDateTime dtNow;dtNow = DateTime.Today;if (!startDate.HasValue){startDate = new DateTime(dtNow.Year, dtNow.Month, 1);endDate = startDate.Value.AddMonths(1).AddDays(-1);}//take last date of start date's month, if end date is not passedif (startDate.HasValue && !endDate.HasValue){endDate = (new DateTime(startDate.Value.Year, startDate.Value.Month, 1)).AddMonths(1).AddDays(-1);}var expenses = expenseService.GetExpenses(startDate.Value ,endDate.Value);//if request is Ajax will return partial viewif (Request.IsAjaxRequest()){return PartialView("ExpenseList", expenses);}//set start date and end date to ViewBag dictionaryViewBag.StartDate = startDate.Value.ToShortDateString();ViewBag.EndDate = endDate.Value.ToShortDateString();//if request is not ajaxreturn View(expenses);} |
上述Index Action方法负责处理AJAX 请求和正常的HTTP 请求,如果是AJAX 请求,则调用PartialView方法,显示ExpenseList 视图。
显示Expense信息的Razor 视图
下面创建Razor 视图显示Expense 信息。
ExpenseList.cshtml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
@model IEnumerable<MyFinance.Domain.Expense><table><tr><th>Actions</th><th>Category</th><th>Transaction</th><th>Date</th><th>Amount</th></tr>@foreach (var item in Model) {<tr><td>@Html.ActionLink("Edit", "Edit",new { id = item.ExpenseId })@Ajax.ActionLink("Delete", "Delete", new { id = item.ExpenseId }, new AjaxOptions { Confirm = "Delete Expense?", HttpMethod = "Post", UpdateTargetId = "divExpenseList" })</td><td>@item.Category.Name</td><td>@item.Transaction</td><td>@String.Format("{0:d}", item.Date)</td><td>@String.Format("{0:F}", item.Amount)</td></tr>}</table><p>@Html.ActionLink("Create New Expense", "Create") |@Html.ActionLink("Create New Category", "Create","Category")</p> |
Index.cshtml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@using MyFinance.Helpers;@model IEnumerable<MyFinance.Domain.Expense>@{ViewBag.Title = "Index";}<h2>Expense List</h2><script src="@Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script><script src="@Url.Content("~/Scripts/jquery-ui.js")" type="text/javascript"></script><script src="@Url.Content("~/Scripts/jquery.ui.datepicker.js")" type="text/javascript"></script><link href="@Url.Content("~/Content/jquery-ui-1.8.6.custom.css")" rel="stylesheet" type="text/css" />@using (Ajax.BeginForm(new AjaxOptions{ UpdateTargetId="divExpenseList", HttpMethod="Get"})) {<table><tr><td><div>Start Date: @Html.TextBox("StartDate", Html.Encode(String.Format("{0:mm/dd/yyyy}", ViewData["StartDate"].ToString())), new { @class = "ui-datepicker" })</div></td><td><div>End Date: @Html.TextBox("EndDate", Html.Encode(String.Format("{0:mm/dd/yyyy}", ViewData["EndDate"].ToString())), new { @class = "ui-datepicker" })</div></td><td> <input type="submit" value="Search By TransactionDate" /></td></tr></table>}<div id="divExpenseList">@Html.Partial("ExpenseList", Model)</div><script type="text/javascript">$().ready(function () {$('.ui-datepicker').datepicker({dateFormat: 'mm/dd/yy',buttonImage: '@Url.Content("~/Content/calendar.gif")',buttonImageOnly: true,showOn: "button"});});</script> |
Ajax 搜索功能使用 Ajax.BeginForm
Index 视图的搜索功能使用Ajax.BeginForm 方法,提供了Ajax 功能。Ajax.BeginForm() 方法在响应中输出 <form> 标签,也可在using 块中使用这一方法。在这一情况下,则在using 语句块的结束处 输出 </form> 结束标签,且使用 Javascript 脚本实现表单的异步提交。搜索功能将调用 Index Action 方法,返回 ExpenseList 部分视图,并更新搜索结果。使用Ajax 请求的响应结果,更新divExpenseList 元素,因此Ajax.BeginForm代码中指定了UpdateTargetId = “divExpenseList”。
添加jQuery 日期选择控件 DatePicker
搜索方法使用日期区间,因为我们提供了2个日期选择控件,使用jQuery datepicker。需要添加对如下JavaScript 文件的引用:
jquery-ui.js
jquery.ui.datepicker.js
为了主题支持日期选择器,我们使用了一个定制的CSS 类。在上述范例中,使用了 jquery-ui-1.8.6.custom.css 文件。关于datepicker 控件的更多信息,请访问jQuery UI 网站:http://jqueryui.com/demos/datepicker。在jQuery Ready 事件中,我们使用了如下JavaScript 方法初始化日期选择器的 UI 元素:
|
1
2
3
4
5
6
7
8
9
10
|
<script type="text/javascript">$().ready(function () {$('.ui-datepicker').datepicker({dateFormat: 'mm/dd/yy',buttonImage: '@Url.Content("~/Content/calendar.gif")',buttonImageOnly: true,showOn: "button"});});</script> |
EFMVC 项目源码及其介绍
EFMVC – ASP.NET MVC 3 and Entity Framework 4.1 Code First 项目介绍
总 结
在这2篇文章中,我们演示了使用ASP.NET MVC 3、Razor 和 EF Code First 技术创建了一个简单的web应用程序,其中包括依赖注入(Dependency Injection)、Repository模式、Unit of Work模式、ViewModel和Service层等等。作者的目的是演示使用ASP.NET MVC 3 不同的技术和方法,开发Web应用程序。
英文原文链接:
Developing web apps using ASP.NET MVC 3, Razor and EF Code First – Part 2

浙公网安备 33010602011771号