使用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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?