若是你正在学习ASP.NET MVC 3,HTML5,jQuery和浏览器客户端交互技术,推荐你下载Mileage Stats 范例程序,可更好理解如何使用当前技术建立当前的web应用程序,尤为关注如何架构一个企业级的应用程序。关于Mileage Stats项目的初步介绍,请参考《Project Silk – 基于ASP.NET MVC 3 的示例应用程序Mileage Stats》。web
EntLib.com Team 尝试从架构的角度对MileageStats项目进行分析和解读,并计划运用到实际的电子商务系统中,欢迎你们参与交流和分享。浏览器
MileageStats RI 运行的主要界面:架构
MileageStats RI 项目的当前架构图,主要有Web 表示层、业务逻辑层和数据访问层,以下图所示。app
简要看看MileageStats 包含的一些主要项目:dom
建立数据模型(Data Model)ide
MileageStats.Model 项目包含数据模型(Data Model)。结构化和强类型的类描述了业务数据的数据类型、关系和约束。学习
实现Repository Patternthis
在Repository模式中,Repository是一组接口,实现了数据访问相关的方法。接口没有暴露任何特定数据存储相关的类型。spa
MileageStats.Data 项目包含了Repository接口,MileageStats.Data.SqlCe 项目包含了接口的实现。以下是IReminderRepository 接口的示例代码:.net
// contained in IReminderRepository.cs
public interface IReminderRepository
{
void Create(int vehicleId, Reminder reminder);
Reminder GetReminder(int reminderId);
void Update(Reminder reminder);
void Delete(int reminderId);
IEnumerable<Reminder> GetRemindersForVehicle(int vehicleId);
IEnumerable<Reminder> GetOverdueReminders(int vehicleId,
DateTime forDate, int forOdometer);
IEnumerable<Reminder> GetUpcomingReminders(int vehicleId,
DateTime forStartDate, DateTime forEndDate,
int odometer, int warningOdometer);
IEnumerable<Reminder> GetFulfilledRemindersForVehicle(int vehicleId);
}
分解应用程序代码到ASP.NET MVC模式
设计良好的MVC应用程序保持Controller和Action方法比较小,View比较简单。大部分的核心应用程序逻辑存放在Model中。在MVC应用程序建立时,保持DRY(Don’t Repeat Yourself)原则比后期试图清理代码更容易。
由于大部分应用程序逻辑在Model层中,所以不少MVC 应用程序包含不一样类型的Model:
l View models(视图模型) - 仅用于视图的数据绑定,这些Models包含于MVC 应用程序中,通常与Views和Partial Views保持相同的构成结构。
l Application, domain, or service models(业务模型) - 基于实际业务须要创建的数据模型,可添加属性标注,或扩展支持应用功能,如数据验证、身份验证。由于这些Models易于往返于客户端浏览器,所以它们常常包含于View Models,并直接在HTML 表单进行数据绑定。
l Data models(数据模型) - 用于数据服务和存储,不会暴露在应用程序以外,常常封装在服务层。
以下是MileageStats RI Solution中包含的3个Model项目 – 下图中选中的项目,分别在Web层、业务逻辑层和数据层:
对于比较复杂或长期维护的应用程序,应该分离业务模型和数据模型。若是业务模型和数据模型的层级和接口差别很大,则建立彻底分离的类。如业务模型和数据模型有匹配的层级和兼容的接口,则建议业务模型类继承数据模型类。如业务模型和数据模型有匹配的层级,但接口不兼容(如数据模型类接口不适合于业务模型类),则在业务模型类中经过汇集关系,包含数据模型类实例。
在编写Controller Action方法时,应将一些复杂的方法包装为model和service层的辅助方法或类中。优先采用action过滤器属性,如HttpPostAttribute,避免在每个action方法中检测HttpContext,编写逻辑判断。此外,使用action过滤器进行横切关切点(Cross-cutting concern),如认证(AuthorizeAttribute)、错误处理(HandleErrorAttribute)等等。处理GET请求的方法应仅包含一些方法调用,而没必要包含太多业务判断逻辑;处理POST 请求的方法应验证传入的数据,在数据合法的状况下,执行更新操做,并根据更新结果,返回对应视图。MileageStats RI 应用程序的以下范例显示2个版本的Add方法(分别为GET和POST版本):
// GET: /Fillups/Add/1
public ActionResult Add(int vehicleId)
{
var vehicles = this.businessServices.GetVehicles(this.User.MileageStatsIdentity().UserId);
Vehicle vehicle = vehicles.First(v => v.VehicleId == vehicleId);
var newFillupEntry = new FillupEntry()
{
Odometer = vehicle.Odometer.HasValue ? vehicle.Odometer.Value : 0
};
var fillups = this.GetVehicleFillupsDescending(vehicleId);
var model = new FillupViewModel()
{
VehicleList = new VehicleListViewModel(vehicles, vehicle) { IsCollapsed = true },
Fillups = new SelectedItemList<FillupEntry>(fillups, newFillupEntry),
FillupEntry = newFillupEntry
};
this.ViewBag.IsFirstFillup = (fillups.Count == 0);
return this.View(model);
}
//
// POST: /Fillups/Add/5
[HttpPost]
[ValidateInput(false)]
[ValidateAntiForgeryToken]
public ActionResult Add(int vehicleId, FillupEntry model)
{
if (this.ModelState.IsValid)
{
this.AddModelErrors(this.businessServices.CanAddFillup(this.CurrentUserId, vehicleId, model),
"AddFillup");
if (this.ModelState.IsValid)
{
this.businessServices.AddFillupToVehicle(this.CurrentUserId, vehicleId, model);
this.TempData["LastActionMessage"] = Resources.VehicleController_AddFillupSuccessMessage;
return this.RedirectToAction("List", "Fillup", new { vehicleId = vehicleId });
}
}
var vehicles = this.businessServices.GetVehicles(this.CurrentUserId);
Vehicle vehicle = vehicles.First(v => v.VehicleId == vehicleId);
var fillups = this.GetVehicleFillupsDescending(vehicleId);
var viewModel = new FillupViewModel()
{
VehicleList = new VehicleListViewModel(vehicles, vehicle) { IsCollapsed = true },
Fillups = new SelectedItemList<FillupEntry>(fillups, model),
FillupEntry = model
};
this.ViewBag.IsFirstFillup = (fillups.Count == 0);
return this.View(viewModel);
}
依赖注入 - Dependency Injection
解耦应用程序的组件(Decoupling the application components)。关于Unity 2.0 依赖注入容器在ASP.NET MVC 3 项目的具体使用细节,可参考文章 - 在ASP.NET MVC 项目使用Unity 2.0实现依赖注入。
建立Business Service 层
为了项目的可维护和支持不一样类型的客户端,大型和复杂的应用程序常常须要额外的架构层 – Business Service Layer,将业务逻辑从数据访问层分离出来。
本文参考和编译了以下文章部份内容:
Project Silk 1.0 – Server-side Architecture
Project Silk - http://silk.codeplex.com/