Lost !

-----hard working for the furture.

导航

统计

使用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)
{
return
 
categories.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 dte
DateTime 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 passed
if (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 view
if (Request.IsAjaxRequest())
{
return PartialView("ExpenseList", expenses);
}
//set start date and end date to ViewBag dictionary
ViewBag.StartDate = startDate.Value.ToShortDateString();
ViewBag.EndDate = endDate.Value.ToShortDateString();
//if request is not ajax
return 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

posted on   失落''80  阅读(384)  评论(0编辑  收藏  举报

编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示