架构指南:ASP.NET, MVC3, Entity Framework, Code-First 配合现有数据库,以及更多..
使用 Entity Framework 的 Code First 技术开发一个简单的 MVC 3 应用程序架构。
推荐框架: https://nidoframework.codeplex.com/[^]
引言
在这篇白皮书中,我将引导您使用 Entity Framework 的 Code First 技术开发一个简单的 MVC 3 应用程序架构。希望稍后您能够将其发展成为可用于中小型 Web 应用程序开发的企业级架构。
尝试从头到尾阅读本文,这样您就能逐步和我一起构建这个架构。
先决条件
- Visual Studio 2010
- SQL Server 2005/ 2008
- 实体框架
- MVC 3
- 创建数据库
创建数据库
在实际场景中,Code-First 技术根本不鼓励您创建数据库。当您编写模型时,系统会自动为您创建相应的数据库。然而,这也会带来一些问题,其中最难忍受的是,每次数据库架构更改时,系统都会重新创建整个数据库。这会导致您丢失数据库中的现有数据。我不想处理这种麻烦,因此作为一种变通方法,我们将采取不同的方法。我们使用 Code-First 技术,但配合一个现有的数据库。作为第一步,与常规的 Code First 技术不同,您必须先创建数据库。下面是我为进行此架构演示而创建的一个简单数据库。
总的来说,在创建数据库时,遵循以下一些最佳实践是很好的:
- 让复合表拥有自己的主键。
- 使用
IsActive
字段来避免从数据库中物理删除记录。否则,当记录被删除时,您将丢失重要信息。 - 养成使用 Name 字段来描述表记录的习惯。在向用户显示记录时,这可以用作记录描述符。
创建 MVC3 项目
在此演示中,我将使用 ASP.NET MVC3 Web 应用程序,因此让我们创建一个名为“MVC3Demo”的项目。我们还将使其成为一个 Razor Internet 应用程序。
我决定为业务逻辑代码创建一个单独的项目。因此,让我们创建一个名为 MVC3Demo.Bll 的类库类型项目。
检查 MVC Web 项目的 Web.Config
我们的 Web 项目的 Web.Config 已定义了连接字符串,如果您仔细查看,会注意到名为‘ApplicationServices’的连接字符串默认使用 SQL-Express 数据库,该数据库随 Visual Studio 2010 一起安装。此配置设置将为您在 App_Data 文件夹内创建一个数据库。这是系统存储应用程序服务的地方,例如成员资格服务(如果您在 MVC 示例项目中创建新用户,那么它会使用成员资格服务,确切地说,是 ‘AspNetSqlMembershipProvider’,在自动创建的 SQL Express 数据库中创建这些用户)相关的记录。然而,我们不想使用该数据库,因此让我们修改它,并为存储我们系统数据对象的数据库添加另一个连接字符串。
修改后的 Web 配置文件将如下所示..
现在,有了这个更改,我们添加了另一个名为 DemoDbContext
的条目,并且您需要使用完全相同的名称来命名我们创建的用于通过 Entity-Framework 与数据库通信的数据库上下文类。此上下文类必须通过继承 .NET Framework 系统类 DbContext
来创建。这样,系统会自动查找名称与继承 DbContext
的类同名的配置标记,以查找连接字符串详细信息。此外,在我们的例子中,我们决定保持应用程序服务设置不变,以便我们可以选择使用单独的数据库进行角色和配置文件管理。
创建成员资格数据库
您需要使用名为 aspnet_regsql 的命令在我们的数据库服务器上新建成员资格数据库。要做到这一点,让我们转到 开始 >> ‘所有程序’ >> ‘Microsoft Visual Studio 2010’ >> ‘Visual Studio Tools’ >> ‘Visual Studio Command Prompt (2010)’,然后输入 'aspnet_regsql'。这将引导您通过一个向导,让您为您的数据库创建‘AccessControlDemo’数据库。
运行项目,并验证新用户是否可以注册到系统中,以及您是否可以使用新创建的用户登录系统。
为您的 BLL 项目安装 ‘EntityFramework’ 包
由于我们的业务逻辑层是类库类型的项目,因此它没有作为 Entity Framework 后端所需的引用。如果您有(它会自动随 MVC 3.0 安装)库包管理器,您可以使用它来为该项目安装所需的‘Entity-Framework DLL’。要做到这一点,在 Visual Studio 2010 的项目中,转到 Tools > Library Package Manager > Package Manager Console。在控制台中,“PM>”提示符之后,输入“install-package entityframework”,这将安装该包并将‘EntityFramework’引用添加到您的 WEB 项目。使用已安装的位置并将 EntityFramework.Dll 引用到您的 BLL 项目。
创建 DemoDbContext 类
如前所述,这必须通过继承 DbContext
类来创建。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
namespace MVC3Demo.Bll
{
public class DemoDbContext : DbContext
{
}
}
完成此操作后,您几乎就完成了项目的设计框架。其余的都是在此已建立的框架之上构建您的系统。
创建模型类
这些模型类与 MVC Web 项目本身的模型类几乎相同。然而,在这种情况下,我们必须创建它们以与我们的数据库表匹配。每个表都必须有单独的模型类,并且必须具有与表字段相同的类型的属性。这种分离将允许我跨多个项目共享我的模型和模型访问逻辑。
通过此技术开发时,您需要特别注意模型及其相应属性的命名。一个拼写错误会破坏整个系统,更糟糕的是,此区域周围生成的系统错误根本没有帮助。
因此,考虑到这一点,让我创建一个名为 Student
的类。尽可能多地使用‘复制’和‘粘贴’来避免拼写错误。我过去是这样复制表记录的,然后..
像这样粘贴到我的代码中..

然后像这样修改它…
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
namespace MVC3Demo.Bll.Models
{
[Table("Student", Schema = "dbo")]
public class Student
{
[ScaffoldColumn(false)]
public int StudentId { get; set; }
[Required]
[StringLength(50, ErrorMessage="{0} cannot exceed {1} characters")]
public string Name { get; set; }
[Required]
public int Age { get; set; }
[Required]
[StringLength(150)]
public string Address { get; set; }
[Required]
[DisplayName("Registered Date")]
public DateTime RegisteredDate { get; set; }
[NotMapped]
public bool SeniorStudent
{
get
{
DateTime today = DateTime.Now;
TimeSpan tSpan = today.Subtract(RegisteredDate);
return (tSpan.TotalDays > 365);
}
}
[Required]
[DisplayName("Is Active")]
public bool IsActive { get; set; }
public string Description { get; set; }
}
}
这里有一个要点,我使用了属性来包含验证逻辑。不仅如此,您还可以定义相应的错误消息并将它们与属性关联起来。此外,您还可以选择在新属性(数据库中不存在)中定义一个具有特殊属性(参见 NotMapped
属性)的类。名为 SeniorStudent
的属性就是这样一个记录。在这种情况下,您需要将其标记为 NotMapped
属性。要了解有关属性的更多信息,请参阅此链接:MSDN。
只需遵循相同的模式,您就可以为另外两个表创建模型类,即‘Course’和‘StudentCourse’表。
教会代码关联相关类
现在,您可能会想,表之间的关系是如何在这里定义的。这很简单,您可以这样教会模型维护其关系..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MVC3Demo.Bll.Models
{
public class StudentCourse
{
public int StudentCourseId { get; set; }
public int CourseId { get; set; }
public Course Course { get; set; }
public int StudentId { get; set; }
public Student Student { get; set; }
}
}
- 由于
StudentCourse
与Student
和Course
类之间存在多对一关系,因此您需要在StudentCourse
类中定义Student
和Course
对象。提示:查找 Id 类型属性,并作为一般实践,在其正下方添加一个同名属性。 - 转到
Student
和Course
类,并添加一个ICollection<entity>
类型的属性,以表示一对多关系的多方。
看看 Course
类现在看起来是怎样的..
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MVC3Demo.Bll.Models
{
public class Course
{
public int CourseId { get; set; }
public string Name { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Period { get; set; }
public bool IsWeekEnd { get; set; }
public bool IsActive { get; set; }
public ICollection<StudentCourse> StudentCourses { get; set; }
}
}
注意:我们使用这种技术在我们工作过的一些地方开发了几个系统,其中一些系统现在已在生产环境中运行。在开发阶段,命名属性时,我们总是建议复制粘贴名称,因为一个小小的拼写错误通常会导致我们难以追踪的错误。然而,这种技术已被证明适合构建中小型系统。
创建控制器
只需编译 Mvc3Demo.Bll 并将其引用添加到主 Web 项目。现在编译 Web 项目,因为在未编译时,Add Controller 向导不会显示在引用的项目中进行的修改。(我相信微软正在积极处理此版本中的一些小问题)。然后,右键单击控制器并填写相应的字段,为我们的模型 Student
、Course
和 StudentCourse
创建‘Controller’以及关联的‘Views’。
编译它,看看是否一切正常。还有最后一个调整。如果您按原样运行此项目并尝试创建新的 Course
,您将收到“Invalid object name 'dbo.Courses'”错误消息。这是因为我们数据库中的表名是单数,而上下文中的列表名称是复数,与表名直接匹配。为避免此问题,您需要如下修改 DemoDbContext
类。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using MVC3Demo.Bll.Models;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace MVC3Demo.Bll
{
public class DemoDbContext : DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<course /> Courses { get; set; }
public DbSet<studentcourse /> StudentCourses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<pluralizingtablenameconvention />();
}
}
}
转到 Global.asax 文件,并修改 register route 方法,将系统指向‘Student’索引页。
然后运行项目,看看一切是否正常..
现在让我们看看‘StudentCourse’的创建视图,看看必需的下拉框是如何自动创建的..
至此,我们已完成了基本系统的开发。然而,如果您查看三个控制器类,您会发现存在重复的代码。为了提高代码的整体质量,我决定添加一个抽象控制器类。这样我就可以避免重复代码,并为系统建立更好的架构。这样,我可以让新加入的开发人员轻松适应代码,并以很小的学习曲线编写高质量的代码。
创建抽象控制器类
在创建此类的过程中,我考虑进行一些额外的重构,如下所示:
- 添加一个名为 Common 的单独文件夹,并将您可能希望在任何项目中通用的几个类添加到此文件夹。这包括异常日志记录、Cookie 处理、web.config 的应用程序设置数据处理,以及一个包含所有常用字符串的类。
- 此外,我决定向同一个 Common 文件夹添加两个扩展。我希望它们将来能派上用场。一个扩展用于
Query
类,另一个用于View
类。 - 添加一个名为 Libraries 的单独文件夹。这将保留此项目引用的所有第三方 DLL。这样,您可以避免在将项目部署到生产环境后出现的一些常见的 DLL 引用相关错误。
- 向 Web 项目的 Model 文件夹添加另一个名为
ConfirmationViewModel
的类。这只是为了演示如何为删除记录调用共享视图。您将在稍后看到它的使用。
注意:随着您开发多个项目,此文件夹中找到的类可以轻松地成长为一个单独的项目。这些项目可以归入您的公司名称下。例如,如果您的公司名称是 XYZ,那么通用项目可以命名为‘Xyz.Common’,让您的公司开发的所有项目引用此项目。通过这种方式,您可以为适用的领域设定公司范围的标准。
通过这些重构,我设法从我的控制器类中移除了几乎所有重复的代码,并将它们实现到我的通用抽象类中。最后,完全翻新的 CourseController
类如下所示:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVC3Demo.Bll.Models;
using MVC3Demo.Bll;
namespace Mvc3Demo.Controllers
{
public class CourseController : BaseController<Course>
{
public CourseController()
: this(new DemoDbContext())
{ }
public CourseController(DemoDbContext db)
: base(db.Courses, db)
{
}
protected override void LoadEditViewBags(Course entity)
{
}
protected override void LoadAddViewBags()
{
}
protected override Type LogPrefix
{
get { return this.GetType(); }
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
重要的基类控制器如下。此代码的一部分取自我们内部公司的一个项目。因此,它包含一些额外的常用方法。即使您决定在您的项目中使用此代码,我也没有任何问题。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MVC3Demo.Bll;
using System.Data.Entity;
using System.Data;
using MvcContrib.UI.Grid;
using log4net;
using System.Reflection;
using System.Data.Entity.Infrastructure;
using Mvc3Demo.Common;
using Mvc3Demo.Models;
namespace Mvc3Demo.Controllers
{
public abstract class BaseController<E> : Controller
where E : class
{
protected DbSet<e /> dbEntitySet;
protected DemoDbContext db;
protected readonly ILog logManager;
public BaseController()
{ }
public BaseController(DbSet<e /> dbSet, DemoDbContext dbContext)
{
dbEntitySet = dbSet;
db = dbContext;
logManager = LogManager.GetLogger(LogPrefix);
}
public BaseController(DemoDbContext dbContext)
{
dbEntitySet = null;
db = dbContext;
logManager = LogManager.GetLogger(LogPrefix);
}
public virtual ViewResult Index()
{
return View(dbEntitySet);
}
//
// GET: /entity/Details/5
/// <summary />
/// Get record of the entity from the database
/// and pass it on to the Details 'PartialView'
/// </summary />
/// <param name="id" />Primary Key of the record to be searched</param />
/// <returns /></returns />
public virtual PartialViewResult Details(int id)
{
return PartialView(GetDetailsData(id));
}
//
// GET: /entity/Create
/// <summary />
/// Create the empty View of the entity
/// </summary />
/// <returns /></returns />
public virtual ActionResult Create()
{
GetCreateData();
return View();
}
//
// POST: /entity/Create
/// <summary />
/// newly create the entity object in the database. calls to the 'GetCreatePostBackData'
/// and then call to the 'LoadEditViewBags' and fianlly return a 'View'
/// </summary />
/// <param name="entity" /></param />
/// <param name="DoNotRedirect" /></param />
/// <returns /></returns />
[HttpPost]
public virtual ActionResult Create(E entity, bool? DoNotRedirect)
{
try
{
int i = GetCreatePostBackData(entity, DoNotRedirect);
if (i < 0)
{
LoadEditViewBags(entity);
return View(entity);
}
else
return Redirect(PortalCookie.Get(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO));
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
// GET: /entity/Delete/5
/// <summary>
/// Delete a record via Ajax delete popup
/// </summary>
/// <param name="id" />
/// <returns>
public virtual ActionResult Delete(int id)
{
try
{
PortalCookie.Set(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO, Request.UrlReferrer.ToString());
ConfirmationViewModel confirm = new ConfirmationViewModel();
confirm.Id = id;
confirm.Action = "Delete";
confirm.Controller = typeof(E).Name;
confirm.OperationName = "Delete";
if (Request.IsAjaxRequest())
return PartialView("Confirmation", confirm);
else
return View("Confirmation", confirm);
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
//
// POST: /entity/Delete/5
/// <summary>
/// Once a delete is confirmed via the popup it will do the DB operation
/// and relavent messages added to the view bag
/// </summary>
/// <param name="id" />
/// <returns>
[HttpPost, ActionName("Delete")]
public virtual ActionResult DeleteConfirmed(int id)
{
try
{
E entity = dbEntitySet.Find(id);
dbEntitySet.Remove(entity);
int i = db.SaveChanges();
this.AddToMessagePlus(i + " record(s) Deleted Successfully!"
, MessageTypes.Success);
return Redirect(PortalCookie.Get(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO));
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
//
// GET: /entity/Edit/5
/// <summary>
/// Update a particular entity.
/// </summary>
/// <param name="id" />
/// <returns>
public virtual ActionResult Edit(int id)
{
try
{
E entity = GetEditData(id);
return View(entity);
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
//
// POST: /entity/Edit/5
[HttpPost]
public virtual ActionResult Edit(E entity, bool? DoNotRedirect)
{
try
{
int i = GetEditPostBackData(entity, DoNotRedirect);
if (i < 0)
{
LoadEditViewBags(entity);
return View(entity);
}
else
return Redirect(PortalCookie.Get(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO));
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
protected int EditEntity(E entity)
{
try
{
if (ModelState.IsValid)
{
UpdateTrackingData(entity, false);
db.Entry(entity).State = EntityState.Modified;
int i = db.SaveChanges();
return i;
}
return -2;
}
catch (Exception e)
{
logManager.Error("Error in Controller", e);
return -1;
}
}
protected int CreateEntity(E entity)
{
try
{
if (ModelState.IsValid)
{
UpdateTrackingData(entity, true);
dbEntitySet.Add(entity);
int i = db.SaveChanges();
return i;
}
return -2;
}
catch (Exception e)
{
this.logManager.Error("Db Related Error Occured", e);
return -1;
}
}
protected IEnumerable<e> GetIndexData()
{
try
{
return UpdateIncludes(dbEntitySet).AsEnumerable();
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
protected E GetDetailsData(int id)
{
try
{
return dbEntitySet.Find(id);
}
catch (Exception e)
{
this.AddToMessage(e.Message, MessageTypes.Error);
logManager.Error("Error in Controller", e);
throw e;
}
}
protected void GetCreateData()
{
PortalCookie.Set(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO, (Request.UrlReferrer == null)
? Request.RawUrl.ToString() : Request.UrlReferrer.ToString());
LoadAddViewBags();
}
protected E GetEditData(int id)
{
PortalCookie.Set(ConstString.URL_DATA, ConstString.URL_REDIRECT_TO, Request.UrlReferrer.ToString());
E entity = dbEntitySet.Find(id);
LoadEditViewBags(entity);
return entity;
}
/// <summary>
/// CreateEntity is called and
/// then relavent message is add to the view bag
/// </summary>
/// <param name="entity" />
/// <param name="DoNotRedirect" />
/// <returns>
protected int GetCreatePostBackData(E entity, bool? DoNotRedirect)
{
int i = CreateEntity(entity);
return AddCreateSatusMessage(DoNotRedirect, i);
}
protected int AddCreateSatusMessage(bool? DoNotRedirect, int i)
{
if (i == ConstString.ERROR_INT)
this.AddToMessage("Error: No record(s) Created!", MessageTypes.Error);
else if (i == ConstString.ERROR_INVALID_OBJECT_INT)
this.AddToMessage("Warning: Some record(s) yet to be filled!", MessageTypes.Warning);
else if ((DoNotRedirect.HasValue) && (DoNotRedirect.Value))
this.AddToMessage(i + " record(s) Created Successfully!", MessageTypes.Success);
else
this.AddToMessagePlus(i + " record(s) Created Successfully!", MessageTypes.Success);
return i;
}
/// <summary>
/// EditEntity is called and
/// then relavent message is add to the view bag
/// </summary>
/// <param name="entity" />
/// <param name="DoNotRedirect" />
/// <returns>
protected int GetEditPostBackData(E entity, bool? DoNotRedirect)
{
int i = EditEntity(entity);
return AddEditStatusMessage(DoNotRedirect, i);
}
protected int AddEditStatusMessage(bool? DoNotRedirect, int i)
{
if (i == ConstString.ERROR_INT)
this.AddToMessage("Error: No record(s) Updated!", MessageTypes.Error);
else if (i == ConstString.ERROR_INVALID_OBJECT_INT)
this.AddToMessage("Warning: Some record(s) yet to be filled!", MessageTypes.Warning);
else if ((DoNotRedirect.HasValue) && (DoNotRedirect.Value))
this.AddToMessage(i + " record(s) Updated Successfully!", MessageTypes.Success);
else
this.AddToMessagePlus(i + " record(s) Updated Successfully!", MessageTypes.Success);
return i;
}
private PagedViewModel<e> createPageViewModel(GridSortOptions gridSortOptions
, int? page, string sortColumn)
{
ShowCurrentMessage();
return new PagedViewModel<e>
{
ViewData = ViewData,
Query = dbEntitySet,
GridSortOptions = gridSortOptions,
DefaultSortColumn = sortColumn,
Page = page,
PageSize = Common.ConstString.PAGE_SIZE,
};
}
private PagedViewModel<e> createPageViewModel(GridSortOptions gridSortOptions
, int? page, string sortColumn, DbQuery<e> dbQuery)
{
ShowCurrentMessage();
return new PagedViewModel<e>
{
ViewData = ViewData,
Query = dbQuery,
GridSortOptions = gridSortOptions,
DefaultSortColumn = sortColumn,
Page = page,
PageSize = Common.ConstString.PAGE_SIZE,
};
}
private void UpdateTrackingData(E entity, bool isNew)
{
this.UpdateTrackingData<e>(entity, isNew);
}
/// <summary>
///
/// </summary>
/// <param name="name" />Name of the view
/// <param name="value" />Select list to be assigned to a drop down box
/// <returns>BaseController itself</returns>
internal BaseController<e> AddToViewBag(string name, SelectList value)
{
ViewData[name] = value;
return this;
}
/// <summary>
/// If you have any dropdown boxes please use this method to load their list
/// to respective view bags.
/// </summary>
/// <example>
/// this.AddToViewBag("EmployeeId", new SelectList(db.Employees, "EmployeeId", "EPFNo"))
/// .AddToViewBag("DowntimeReasonId", new SelectList(db.DowntimeReasons, "DowntimeReasonId", "Name"))
/// </example>
protected abstract void LoadAddViewBags();
protected abstract void LoadEditViewBags(E entity);
protected virtual IQueryable<e> UpdateIncludes(DbSet<e> dbEntitySet)
{
return dbEntitySet.AsQueryable<e>();
}
protected void UpdateTrackingData<t>(T entity, bool isNew)
{
PropertyInfo pInfoMBy = entity.GetType().GetProperty(ConstString.PROPERTY_MODIFY_BY);
if (pInfoMBy != null)
pInfoMBy.SetValue(entity, Convert.ChangeType(User.Identity.Name, pInfoMBy.PropertyType), null);
PropertyInfo pInfoMDate = entity.GetType().GetProperty(ConstString.PROPERTY_MODIFY_DATE);
if (pInfoMDate != null)
pInfoMDate.SetValue(entity, Convert.ChangeType(DateTime.Now, pInfoMDate.PropertyType), null);
PropertyInfo pInfoABy = entity.GetType().GetProperty(ConstString.PROPERTY_ADDED_DATE);
if (pInfoABy != null)
pInfoABy.SetValue(entity, Convert.ChangeType(DateTime.Now, pInfoABy.PropertyType), null);
}
/// <summary>
/// Return this.GetType(); This name is used to trace
/// error locations of the log message write via log4net library.
/// </summary>
protected abstract Type LogPrefix
{
get;
}
public class CommonMessage
{
public CommonMessage()
{
this.Message = "";
this.MessageType = MessageTypes.None;
}
public CommonMessage(string message, MessageTypes mType)
{
this.Message = message;
this.MessageType = mType;
}
public string Message { get; set; }
public MessageTypes MessageType { get; set; }
}
public CommonMessage GetFromMessagePlus()
{
string[] tempArray = TempData.GetMessageSummary();
if (tempArray == null)
return null;
if (tempArray.Length > 0)
{
string[] mesgs = tempArray[0].Split('|');
int t;
if (int.TryParse(mesgs[1], out t))
return new CommonMessage(mesgs[0], (MessageTypes)t);
}
return null;
}
public void AddToMessagePlus(string message, MessageTypes mtype)
{
TempData.AddStatusMessage(message + "|" + (int)mtype);
}
public void ShowCurrentMessage()
{
CommonMessage temp = GetFromMessagePlus();
if (temp != null)
AddToMessage(temp.Message, temp.MessageType);
}
public void AddToMessage(string message, MessageTypes mtype)
{
switch (mtype)
{
case MessageTypes.Success:
{
ViewBag.successbox = message;
} break;
case MessageTypes.Warning:
{
ViewBag.warningbox = message;
} break;
case MessageTypes.Error:
{
ViewBag.errormsgbox = message;
} break;
}
}
}
public enum MessageTypes
{
Error,
Warning,
Success,
None
}
}
您需要研究这个类,看看如何进一步改进它。与我的一些其他文章不同,我将不解释这个类的每一行代码,但如果您有任何具体问题,请将其作为问题类型的评论发布到本文,我将尽快回复您。
使 Grid 可排序
有很多花哨的 Grid 视图可以与 MVC2 Razor 视图一起使用。是的,正如你们都知道的,这方面还需要很多改进(MSFT 请注意)。在我看来,其中最好的就是 jQuery grid,但我认为使用更基础简单的‘MvcContrib.Gird’就足够了。
您可以在这里了解更多关于如何集成‘MVCContrib’ grid 的信息:http://mvccontrib.codeplex.com/wikipage?title=Grid。
在集成 grid 的过程中,我不得不从我的抽象类中删除 Index
方法(Action)的实现。这在我与 CourseController
类中想要的新方法之间造成了歧义。因此,如果我使用 MVCContrib grid,我有两种可能的设计选项:
- 从抽象基类控制器中删除
Index
方法的实现,并在具体的控制器类中实现它。 - 为在具体控制器中执行操作实现定义一个新的 View,同时保留抽象控制器的
Index
方法。
我决定选择第一种方案,因为它使我的设计更简单,并且提供了相对较少的选择 。软件设计有一个著名的理论,‘关键不在于添加,而在于移除所有不必要的’。让我们来实践一下。
新修改的 Course 的 Index ‘View’的外观如下:
最终的‘Course’ Index view 如下所示: