使用ASP.NET MVC 3、Razor和Entity Framework Code First技术开发Web应用程序 – Part 1
ASP.NET MVC 3 是一个框架,它通过利用模型视图控制器 (MVC) 模式来开发可测试性和可维护性很高的 Web 应用程序。该框架鼓励开发人员明确地分离应用程序各职责之间的任务 – 使用视图的 UI 逻辑、使用控制器的用户输入处理以及使用模型的域逻辑。通过使用诸如测试驱动的开发 (TDD) 等技术轻松测试 ASP.NET MVC 应用程序。
本文演示如何使用ASP.NET MVC 3、Razor和 EF Code First 技术开发web应用程序,其中包括使用Unity 2.0依赖注入、Repository模式等等。可访问http://efmvc.codeplex.com 下载本范例应用程序源码。
定义业务Model
为范例web应用程序创建业务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
10
|
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 ; } } |
上面定义了2个业务实体 – Category和Expense。每一个Category包含一个Expense事务列表,每一个Expense事务应该属于一个Category。我 们将开发一个Expense跟踪应用程序,并更新Expense事务为特定的Category。本文将专注于Category实体的CRUD操作,下一篇 将专注于Expense 实体。
上述实体是非常简单的POCO类(Plain Old CLR Object),引用了System.ComponentModel.DataAnnotations命名空间,在Category上类标注了验证属性。
using System.ComponentModel.DataAnnotations;
为Entity Framework创建Context类
上面创建了业务Model,为了应用Entity Framework Code First,下面创建一个Context类。第一步需要下载EF Code First,并添加对EntityFramework.dll 的引用。另外,也可使用NuGet下载并添加对EF Code First的引用。
1
2
3
4
5
6
|
public class MyFinanceContext : DbContext { public MyFinanceContext() : base ( "MyFinance" ) { } public DbSet<Category> Categories { get ; set ; } public DbSet<Expense> Expenses { get ; set ; } } |
MyFinanceContext类继承DbContext,可连接Model类到数据库。MyFinanceContext 类使用DbSet<TEntity> (TEntity 是一个POCO类),分别映射Category和Expense 类到数据库表Categories和Expenses。当在第一次运行应用程序时,将自动创建数据库。EF Code First在Web.config 或 app.config 中寻找和DbContext类相同名称的连接字符串。如没有找到对应的connection string,则自动在本地SQL Express创建和DbContext类名称相同的数据库,也可在DbContext类的构造函数中定义数据库的名称。
基于EF Code First的Repository模式
上面创建了Model 类和DbContext类,这里创建数据持久化类的Repository模式。如不了解Repository模式,可参考Martin Fowler关于Repository模式的文章或者《Repository 设计模式》。
创建IRepository接口
1
2
3
4
5
6
7
8
9
|
public interface IRepository<T> where T : class { void Add(T entity); void Delete(T entity); T GetById( long Id); T Get(Func<T, Boolean> where); IEnumerable<T> GetAll(); IEnumerable<T> GetMany(Func<T, bool > where); } |
RepositoryBase基类
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
46
47
|
public abstract class RepositoryBase<T> where T : class { private MyFinanceContext dataContext; private readonly IDbSet<T> dbset; protected RepositoryBase(IDatabaseFactory databaseFactory) { DatabaseFactory = databaseFactory; dbset = DataContext.Set<T>(); } protected IDatabaseFactory DatabaseFactory { get ; private set ; } protected MyFinanceContext DataContext { get { return dataContext ?? (dataContext = DatabaseFactory.Get()); } } public virtual void Add(T entity) { dbset.Add(entity); } public virtual void Delete(T entity) { dbset.Remove(entity); } public virtual T GetById( long id) { return dbset.Find(id); } public virtual IEnumerable<T> GetAll() { return dbset.ToList(); } public virtual IEnumerable<T> GetMany(Func<T, bool > where) { return dbset.Where(where).ToList(); } public T Get(Func<T, Boolean> where) { return dbset.Where(where).FirstOrDefault<T>(); } } |
DatabaseFactory 类
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class DatabaseFactory : Disposable, IDatabaseFactory { private MyFinanceContext dataContext; public MyFinanceContext Get() { return dataContext ?? (dataContext = new MyFinanceContext()); } protected override void DisposeCore() { if (dataContext != null ) dataContext.Dispose(); } } |
上述Repository相关类仅仅是一个基本的版本,在后面需进行重构。
Unit of Work 模式
如对Unit of Work 模式不熟悉,可参考Fowler的文章Unit of Work或者《Unit Of Work模式》。Unit of Work 模式负责维护业务事务影响的对象列表,并协调更新和同步问题。
创建一个类,处理Unit of Work:
1
2
3
4
|
public interface IUnitOfWork { void Commit(); } |
UnitofWork类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public class UnitOfWork : IUnitOfWork { private readonly IDatabaseFactory databaseFactory; private MyFinanceContext dataContext; public UnitOfWork(IDatabaseFactory databaseFactory) { this .databaseFactory = databaseFactory; } protected MyFinanceContext DataContext { get { return dataContext ?? (dataContext = databaseFactory.Get()); } } public void Commit() { DataContext.Commit(); } } |
UnitOfWork的Commit方法将调用MyFinanceContext类的Commit方法,并执行DbContext类的SaveChanges方法。
Category 的Repository类
本文将专注Category实体类的持久化操作。继承RepositoryBase<T> 基类,创建CategoryRepository 类负责Category 实体类的CRUD操作。
1
2
3
4
5
6
7
8
9
10
11
|
public class CategoryRepository: RepositoryBase<Category>, ICategoryRepository { public CategoryRepository(IDatabaseFactory databaseFactory) : base (databaseFactory) { } } public interface ICategoryRepository : IRepository<Category> { } |
如需要添加RepositoryBase 基类之外的其他方法,可直接在CategoryRepository类中定义。
使用Unity 2.0实现依赖注入
如对依赖注入或Unity不熟悉,可参考文章《在ASP.NET MVC 项目使用Unity 2.0实现依赖注入》,这里不再重复描述。
开发ASP.NET MVC 3 Web应用程序
前面已经创建了业务Model类、Repository类,并配置好了Unity容器,使用依赖注入。现在,创建Controller类和View视图,实现对Category实体的CRUD操作。
创建Category Controller类
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
public class CategoryController : Controller { private readonly ICategoryRepository categoryRepository; private readonly IUnitOfWork unitOfWork; public CategoryController(ICategoryRepository categoryRepository, IUnitOfWork unitOfWork) { this .categoryRepository = categoryRepository; this .unitOfWork = unitOfWork; } public ActionResult Index() { var categories = categoryRepository.All(); return View(categories); } [HttpGet] public ActionResult Edit( int id) { var category = categoryRepository.GetById(id); return View(category); } [HttpPost] public ActionResult Edit( int id, FormCollection collection) { var category = categoryRepository.GetById(id); if (TryUpdateModel(category)) { unitOfWork.Commit(); return RedirectToAction( "Index" ); } else return View(category); } [HttpGet] public ActionResult Create() { var category = new Category(); return View(category); } [HttpPost] public ActionResult Create(Category category) { if (!ModelState.IsValid) { return View( "Create" , category); } categoryRepository.Add(category); unitOfWork.Commit(); return RedirectToAction( "Index" ); } [HttpPost] public ActionResult Delete( int id) { var category = categoryRepository.GetById(id); categoryRepository.Delete(category); unitOfWork.Commit(); var categories = categoryRepository.All(); return PartialView( "CategoryList" , categories); } } |
创建Razor视图
现在创建Razor 视图,首先创建Partial视图 – CategoryList.cshtml用来显示category列表信息,并提供Edit和Delete 操作的链接。
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
|
@using MyFinance.Helpers; @using MyFinance.Domain; @model IEnumerable< Category > < table > < tr > < th >Actions</ th > < th >Name</ th > < th >Description</ th > </ tr > @foreach (var item in Model) { < tr > < td > @Html.ActionLink("Edit", "Edit",new { id = item.CategoryId }) @Ajax.ActionLink("Delete", "Delete", new { id = item.CategoryId }, new AjaxOptions { Confirm = "Delete Expense?", HttpMethod = "Post", UpdateTargetId = "divCategoryList" }) </ td > < td > @item.Name </ td > < td > @item.Description </ td > </ tr > } </ table > < p > @Html.ActionLink("Create New", "Create") </ p > |
Delete链接使用Ajax.ActionLink 提供Ajax 功能,Ajax异步请求CategoryController类的Delete Action方法。在删除记录后,Delete Action方法将返回Partial 视图 – CategoryList。
创建Index视图,其中包含CategoryList 部分视图
1
2
3
4
5
6
7
8
9
|
@model IEnumerable< MyFinance.Domain.Category > @{ ViewBag.Title = "Index"; } < h2 >Category List</ h2 > < script src = "@Url.Content(" ~/Scripts/jquery.unobtrusive-ajax.min.js")" type = "text/javascript" ></ script > < div id = "divCategoryList" > @Html.Partial("CategoryList", Model) </ div > |
其中使用Html.Partial 辅助方法调用Partial视图,下面创建新增和更新功能的视图页面。这两个视图有相同的用户界面,用来输入Category信息。因此,为 Category信息创建一个EditorTemplate,该EditorTemplate名称需要和实体对象名称保持一致,这样可在视图页面使用 @Html.EditorFor(model=>model) 引用。
创建Category.cshtml 视图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@model MyFinance.Domain.Category < div > @Html.LabelFor(model => model.Name) </ div > < div > @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) </ div > < div > @Html.LabelFor(model => model.Description) </ div > < div > @Html.EditorFor(model => model.Description) @Html.ValidationMessageFor(model => model.Description) </ div > |
下面创建新增Category信息的视图页面
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
|
@model MyFinance.Domain.Category @{ ViewBag.Title = "Save"; } < h2 >Create</ h2 > < script src = "@Url.Content(" ~/Scripts/jquery.validate.min.js")" type = "text/javascript" ></ script > < script src = "@Url.Content(" ~/Scripts/jquery.validate.unobtrusive.min.js")" type = "text/javascript" ></ script > @using (Html.BeginForm()) { @Html.ValidationSummary(true) < fieldset > < legend >Category</ legend > @Html.EditorFor(model => model) < p > < input type = "submit" value = "Create" /> </ p > </ fieldset > } < div > @Html.ActionLink("Back to List", "Index") </ div > |
ViewStart 文件
在Razor视图中,我们在Views目录下添加一个名称为 _viewtart.cshtml文件,该文件将在Views目录下所有视图文件共享。如下为_viewstart.cshtml 文件,为所有视图设置页面布局。
@{
Layout = “~/Views/Shared/_Layout.cshtml”;
}
总 结
本文使用ASP.NET MVC 3 和 EF Code First技术创建了一个简单的Web应用程序,其中讨论了 ASP.NET MVC 3、Razor、EF Code First、Unity 2、Repository模式和Unit of Work 模式等等技术。
英文原文链接:
Developing web apps using ASP.NET MVC 3, Razor and EF Code First – Part 1
【推荐】国内首个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如何颠覆传统软件测试?测试工程师会被淘汰吗?