MVC实用构架实战:项目结构搭建

开发 架构
在本文中,将使用代码的方式来一一解说各个层次。由于要搭建一个基本完整的结构,可能文章会比较长。另外,本系列主要出于实用的目的,因而并不会严格按照传统的三层那样进行非常明确的层次职能划分。

一、前言

在本文中,将使用代码的方式来一一解说各个层次。由于要搭建一个基本完整的结构,可能文章会比较长。另外,本系列主要出于实用的目的,因而并不会严格按照传统的三层那样进行非常明确的层次职能划分。

二、需求说明

在本系列中,为方便大家理解,将以一个账户管理的小系统来进行解说,具体需求如下:

  1. 用户信息分主要信息与扩展信息,一个用户可以有(或没有)一个用户扩展信息。
  2. 记录用户的登录记录,一个用户可以有多条登录记录,但登录记录所属用户唯一。
  3. 一个用户可以有多个角色,一个角色也可以分配给多个用户。

三、架构基础

(一) 功能返回值

对于一个操作性业务功能(比如添加,修改,删除),通常我们处理返回值的做法是使用简单类型,通常会有如下几种方案:

  1. 直接返回void,即什么也不返回,在操作过程中抛出异常,只要没有异常抛出,就认为是操作成功了
  2. 返回是否操作成功的bool类型的返回值
  3. 返回操作变更后的新数据信息
  4. 返回表示各种结果的状态码的返回值
  5. 返回一个自定义枚举来表示操作的各种结果
  6. 如果要返回多个值,还要使用 out 来添加返回参数

这样做有什么不妥之处呢,我们来逐一分析:

  1. 靠抛异常的方式来终止系统的运行,异常是沿调用堆栈逐层向上抛出的,会造成很大的性能问题
  2. bool值太死板,无法表示出业务操作中的各种情况
  3. 返回变更后的数据,还要与原始数据来判断才能得到是否操作成功
  4. 用状态码解决了2的问题,但各种状态码的维护成本也会非常高
  5. 用枚举值一定程序上解决了翻译的问题,但还是要把枚举值翻译成各种情况的文字描述
  6. !@#¥%……&

综上,我们到底需要一个怎样的业务操作结果呢?

  1. 要能表示操作的成功失败(废话)
  2. 要能快速表示各种操作场景(如参数错误,查询数据不存在,数据状态不满足操作要求等)
  3. 能返回附加的返回信息(如更新成功后有后续操作,需要使用更新后的新值)
  4. 最好在调用方能使用统一的代码进行返回值处理
  5. 最好能自定义返回的文字描述信息
  6. 最好能把返回给用户的信息与日志记录的信息分开

再综上,显然简单类型的返回值满足不了需求了,那就需要定义一个专门用来封装返回值信息的返回值类,这里定义如下: 

  1. /// <summary>  
  2.      ///     业务操作结果信息类,对操作结果进行封装  
  3.      /// </summary>  
  4.      public class OperationResult  
  5.      {  
  6.          #region 构造函数  
  7.    
  8.          /// <summary>  
  9.          ///     初始化一个 业务操作结果信息类 的新实例  
  10.          /// </summary>  
  11.          /// <param name="resultType">业务操作结果类型</param>  
  12.          public OperationResult(OperationResultType resultType)  
  13.          {  
  14.              ResultType = resultType;  
  15.          }  
  16.    
  17.          /// <summary>  
  18.          ///     初始化一个 定义返回消息的业务操作结果信息类 的新实例  
  19.          /// </summary>  
  20.          /// <param name="resultType">业务操作结果类型</param>  
  21.          /// <param name="message">业务返回消息</param>  
  22.          public OperationResult(OperationResultType resultType, string message)  
  23.              : this(resultType)  
  24.          {  
  25.              Message = message;  
  26.          }  
  27.    
  28.          /// <summary>  
  29.          ///     初始化一个 定义返回消息与附加数据的业务操作结果信息类 的新实例  
  30.          /// </summary>  
  31.          /// <param name="resultType">业务操作结果类型</param>  
  32.          /// <param name="message">业务返回消息</param>  
  33.          /// <param name="appendData">业务返回数据</param>  
  34.          public OperationResult(OperationResultType resultType, string message, object appendData)  
  35.              : this(resultType, message)  
  36.          {  
  37.              AppendData = appendData;  
  38.          }  
  39.    
  40.          /// <summary>  
  41.          ///     初始化一个 定义返回消息与日志消息的业务操作结果信息类 的新实例  
  42.          /// </summary>  
  43.          /// <param name="resultType">业务操作结果类型</param>  
  44.          /// <param name="message">业务返回消息</param>  
  45.          /// <param name="logMessage">业务日志记录消息</param>  
  46.          public OperationResult(OperationResultType resultType, string message, string logMessage)  
  47.              : this(resultType, message)  
  48.          {  
  49.              LogMessage = logMessage;  
  50.          }  
  51.    
  52.          /// <summary>  
  53.          ///     初始化一个 定义返回消息、日志消息与附加数据的业务操作结果信息类 的新实例  
  54.          /// </summary>  
  55.          /// <param name="resultType">业务操作结果类型</param>  
  56.          /// <param name="message">业务返回消息</param>  
  57.          /// <param name="logMessage">业务日志记录消息</param>  
  58.          /// <param name="appendData">业务返回数据</param>  
  59.          public OperationResult(OperationResultType resultType, string message, string logMessage, object appendData)  
  60.              : this(resultType, message, logMessage)  
  61.          {  
  62.              AppendData = appendData;  
  63.          }  
  64.    
  65.          #endregion  
  66.    
  67.          #region 属性  
  68.    
  69.          /// <summary>  
  70.          ///     获取或设置 操作结果类型  
  71.          /// </summary>  
  72.          public OperationResultType ResultType { get; set; }  
  73.    
  74.          /// <summary>  
  75.          ///     获取或设置 操作返回信息  
  76.          /// </summary>  
  77.          public string Message { get; set; }  
  78.    
  79.          /// <summary>  
  80.          ///     获取或设置 操作返回的日志消息,用于记录日志  
  81.          /// </summary>  
  82.          public string LogMessage { get; set; }  
  83.    
  84.          /// <summary>  
  85.          ///     获取或设置 操作结果附加信息  
  86.          /// </summary>  
  87.          public object AppendData { get; set; }  
  88.    
  89.          #endregion  
  90.      } 

再定义一个表示业务操作结果的枚举,枚举项上有一个DescriptionAttribute的特性,用来作为当上面的Message为空时的返回结果描述。

  1. /// <summary>  
  2.      ///     表示业务操作结果的枚举  
  3.      /// </summary>  
  4.      [Description("业务操作结果的枚举")]  
  5.      public enum OperationResultType  
  6.      {  
  7.          /// <summary>  
  8.          ///     操作成功  
  9.          /// </summary>  
  10.          [Description("操作成功。")]  
  11.          Success,  
  12.    
  13.          /// <summary>  
  14.          ///     操作取消或操作没引发任何变化  
  15.          /// </summary>  
  16.          [Description("操作没有引发任何变化,提交取消。")]  
  17.          NoChanged,  
  18.    
  19.          /// <summary>  
  20.          ///     参数错误  
  21.          /// </summary>  
  22.          [Description("参数错误。")]  
  23.          ParamError,  
  24.    
  25.          /// <summary>  
  26.          ///     指定参数的数据不存在  
  27.          /// </summary>  
  28.          [Description("指定参数的数据不存在。")]  
  29.          QueryNull,  
  30.    
  31.          /// <summary>  
  32.          ///     权限不足  
  33.          /// </summary>  
  34.          [Description("当前用户权限不足,不能继续操作。")]  
  35.          PurviewLack,  
  36.    
  37.          /// <summary>  
  38.          ///     非法操作  
  39.          /// </summary>  
  40.          [Description("非法操作。")]  
  41.          IllegalOperation,  
  42.    
  43.          /// <summary>  
  44.          ///     警告  
  45.          /// </summary>  
  46.          [Description("警告")]  
  47.          Warning,  
  48.    
  49.          /// <summary>  
  50.          ///     操作引发错误  
  51.          /// </summary>  
  52.          [Description("操作引发错误。")]  
  53.          Error,  
  54.      } 

#p#

(二) 实体基类

对于业务实体,有一些相同的且必要的信息,比如信息的创建时间,总是必要的;再比如想让数据库有一个“回收站”的功能,以给数据删除做个缓冲,或者很多数据并非想从数据库中彻底删除掉,只是暂时的“禁用”一下,添加个逻辑删除的标记也是必要的。再有就是想给所有实体数据仓储操作来个类型限定,以防止传入了其他非实体类型。基于以上理由,就有了下面这个实体基类:

  1. /// <summary>  
  2.      ///     可持久到数据库的领域模型的基类。  
  3.      /// </summary>  
  4.      [Serializable]  
  5.      public abstract class Entity  
  6.      {  
  7.          #region 构造函数  
  8.    
  9.          /// <summary>  
  10.          ///     数据实体基类  
  11.          /// </summary>  
  12.          protected Entity()  
  13.          {  
  14.              IsDeleted = false;  
  15.              AddDate = DateTime.Now;  
  16.          }  
  17.    
  18.          #endregion  
  19.    
  20.          #region 属性  
  21.    
  22.          /// <summary>  
  23.          ///     获取或设置 获取或设置是否禁用,逻辑上的删除,非物理删除  
  24.          /// </summary>  
  25.          public bool IsDeleted { get; set; }  
  26.    
  27.          /// <summary>  
  28.          ///     获取或设置 添加时间  
  29.          /// </summary>  
  30.          [DataType(DataType.DateTime)]  
  31.          public DateTime AddDate { get; set; }  
  32.    
  33.          /// <summary>  
  34.          ///     获取或设置 版本控制标识,用于处理并发  
  35.          /// </summary>  
  36.          [ConcurrencyCheck]  
  37.          [Timestamp]  
  38.          public byte[] Timestamp { get; set; }  
  39.    
  40.          #endregion  
  41.      } 

这里要补充一下,本来实体基类中是可以定义一个表示“实体编号”的Id属性的,但有个问题,如果定义了,就限定了Id属性的数据类型了,但实际需求中可能有些实体使用自增的int类型,有些实体使用的是易于数据合并的guid类型,因此为灵活方便,不在此限制住 Id的数据类型。

四、架构分层

具体的架构分层如下图所示:

(一) 核心业务层

根据 需求说明 中定义的需求,简单起见,这里只实现一个简单的用户登录功能:

用户信息实体:

  1. /// <summary>  
  2.      ///     实体类——用户信息  
  3.      /// </summary>  
  4.      [Description("用户信息")]  
  5.      public class Member : Entity  
  6.      {  
  7.          /// <summary>  
  8.          /// 获取或设置 用户编号  
  9.          /// </summary>  
  10.          public int Id { get; set; }  
  11.    
  12.          /// <summary>  
  13.          /// 获取或设置 用户名  
  14.          /// </summary>  
  15.          [Required]  
  16.          [StringLength(20)]  
  17.          public string UserName { get; set; }  
  18.    
  19.          /// <summary>  
  20.          /// 获取或设置 密码  
  21.          /// </summary>  
  22.          [Required]  
  23.          [StringLength(32)]  
  24.          public string Password { get; set; }  
  25.    
  26.          /// <summary>  
  27.          /// 获取或设置 用户昵称  
  28.          /// </summary>  
  29.          [Required]  
  30.          [StringLength(20)]  
  31.          public string NickName { get; set; }  
  32.    
  33.          /// <summary>  
  34.          /// 获取或设置 用户邮箱  
  35.          /// </summary>  
  36.          [Required]  
  37.          [StringLength(50)]  
  38.          public string Email { get; set; }  
  39.    
  40.          /// <summary>  
  41.          /// 获取或设置 用户扩展信息  
  42.          /// </summary>  
  43.          public virtual MemberExtend Extend { get; set; }  
  44.    
  45.          /// <summary>  
  46.          /// 获取或设置 用户拥有的角色信息集合  
  47.          /// </summary>  
  48.          public virtual ICollection<Role> Roles { get; set; }  
  49.    
  50.          /// <summary>  
  51.          /// 获取或设置 用户登录记录集合  
  52.          /// </summary>  
  53.          public virtual ICollection<LoginLog> LoginLogs { get; set; }  
  54.      } 

核心业务契约:注意接口的返回值使用了上面定义的返回值类

  1. /// <summary>  
  2.      ///     账户模块核心业务契约  
  3.      /// </summary>  
  4.      public interface IAccountContract  
  5.      {  
  6.          /// <summary>  
  7.          /// 用户登录  
  8.          /// </summary>  
  9.          /// <param name="loginInfo">登录信息</param>  
  10.          /// <returns>业务操作结果</returns>  
  11.          OperationResult Login(LoginInfo loginInfo);  
  12.      } 

核心业务实现:核心业务实现类为抽象类,因没有数据访问功能,这里使用了一个Members字段来充当数据源,业务功能的实现为虚方法,必要时可以在具体的客户端(网站、桌面端,移动端)相应的派生类中进行重写。请注意具体实现中对于返回值的处理。这里登录只负责最核心的登录业务操作,不涉及比如Http上下文状态的操作。

  1. /// <summary>  
  2.      ///     账户模块核心业务实现  
  3.      /// </summary>  
  4.      public abstract class AccountService : IAccountContract  
  5.      {  
  6.          private static readonly Member[] Members = new[]  
  7.          {  
  8.              new Member { UserName = "admin", Password = "123456", Email = "admin@gmfcn.net", NickName = "管理员" },  
  9.              new Member { UserName = "gmfcn", Password = "123456", Email = "mf.guo@qq.com", NickName = "郭明锋" }  
  10.          };  
  11.    
  12.          private static readonly List<LoginLog> LoginLogs = new List<LoginLog>();  
  13.    
  14.          /// <summary>  
  15.          /// 用户登录  
  16.          /// </summary>  
  17.          /// <param name="loginInfo">登录信息</param>  
  18.          /// <returns>业务操作结果</returns>  
  19.          public virtual OperationResult Login(LoginInfo loginInfo)  
  20.          {  
  21.              PublicHelper.CheckArgument(loginInfo, "loginInfo");  
  22.              Member member = Members.SingleOrDefault(m => m.UserName == loginInfo.Access || m.Email == loginInfo.Access);  
  23.              if (member == null)  
  24.              {  
  25.                  return new OperationResult(OperationResultType.QueryNull, "指定账号的用户不存在。");  
  26.              }  
  27.              if (member.Password != loginInfo.Password)  
  28.              {  
  29.                  return new OperationResult(OperationResultType.Warning, "登录密码不正确。");  
  30.              }  
  31.              LoginLog loginLog = new LoginLog { IpAddress = loginInfo.IpAddress, Member = member };  
  32.              LoginLogs.Add(loginLog);  
  33.              return new OperationResult(OperationResultType.Success, "登录成功。", member);  
  34.          }  
  35.      } 

(二) 站点业务层

站点业务契约:站点业务契约继承核心业务契约,即可拥有核心层定义的业务功能。站点登录验证使用了Forms的Cookie验证,这里的退出不涉及核心层的操作,因而核心层没有退出功能

  1. /// <summary>  
  2.      ///     账户模块站点业务契约  
  3.      /// </summary>  
  4.      public interface IAccountSiteContract : IAccountContract  
  5.      {  
  6.          /// <summary>  
  7.          ///     用户登录  
  8.          /// </summary>  
  9.          /// <param name="model">登录模型信息</param>  
  10.          /// <returns>业务操作结果</returns>  
  11.          OperationResult Login(LoginModel model);  
  12.    
  13.          /// <summary>  
  14.          ///     用户退出  
  15.          /// </summary>  
  16.          void Logout();  
  17.      } 

站点业务实现:站点业务实现继承核心业务实现与站点业务契约,负责把从UI中接收到的视图模型信息转换为符合核心层定义的参数,并处理与网站状态相关的Session,Cookie等Http相关业务

  1. /// <summary>  
  2.      ///     账户模块站点业务实现  
  3.      /// </summary>  
  4.      public class AccountSiteService : AccountService, IAccountSiteContract  
  5.      {  
  6.          /// <summary>  
  7.          ///     用户登录  
  8.          /// </summary>  
  9.          /// <param name="model">登录模型信息</param>  
  10.          /// <returns>业务操作结果</returns>  
  11.          public OperationResult Login(LoginModel model)  
  12.          {  
  13.              PublicHelper.CheckArgument(model, "model");  
  14.              LoginInfo loginInfo = new LoginInfo  
  15.              {  
  16.                  Access = model.Account,  
  17.                  Password = model.Password,  
  18.                  IpAddress = HttpContext.Current.Request.UserHostAddress  
  19.              };  
  20.              OperationResult result = base.Login(loginInfo);  
  21.              if (result.ResultType == OperationResultType.Success)  
  22.              {  
  23.                  Member member = (Member)result.AppendData;  
  24.                  DateTime expiration = model.IsRememberLogin  
  25.                      ? DateTime.Now.AddDays(7)  
  26.                      : DateTime.Now.Add(FormsAuthentication.Timeout);  
  27.                  FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, member.UserName, DateTime.Now, expiration,  
  28.                      true, member.NickName, FormsAuthentication.FormsCookiePath);  
  29.                  HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));  
  30.                  if (model.IsRememberLogin)  
  31.                  {  
  32.                      cookie.Expires = DateTime.Now.AddDays(7);  
  33.                  }  
  34.                  HttpContext.Current.Response.Cookies.Set(cookie);  
  35.                  result.AppendData = null;  
  36.              }  
  37.              return result;  
  38.          }  
  39.    
  40.          /// <summary>  
  41.          ///     用户退出  
  42.          /// </summary>  
  43.          public void Logout()  
  44.          {  
  45.              FormsAuthentication.SignOut();  
  46.          }  
  47.      } 

#p#

(三) 站点展现层

MVC控制器:Action提供统一风格的代码来对业务操作结果OperationResult进行处理

  1. public class AccountController : Controller  
  2.      {  
  3.          public AccountController()  
  4.          {  
  5.              AccountContract = new AccountSiteService();  
  6.          }  
  7.    
  8.          #region 属性  
  9.    
  10.          public IAccountSiteContract AccountContract { get; set; }  
  11.    
  12.          #endregion  
  13.    
  14.          #region 视图功能  
  15.    
  16.          public ActionResult Login()  
  17.          {  
  18.              string returnUrl = Request.Params["returnUrl"];  
  19.              returnUrl = returnUrl ?? Url.Action("Index""Home"new { area = "" });  
  20.              LoginModel model = new LoginModel  
  21.              {  
  22.                  ReturnUrl = returnUrl  
  23.              };  
  24.              return View(model);  
  25.          }  
  26.    
  27.          [HttpPost]  
  28.          public ActionResult Login(LoginModel model)  
  29.          {  
  30.              try 
  31.              {  
  32.                  OperationResult result = AccountContract.Login(model);  
  33.                  string msg = result.Message ?? result.ResultType.ToDescription();  
  34.                  if (result.ResultType == OperationResultType.Success)  
  35.                  {  
  36.                      return Redirect(model.ReturnUrl);  
  37.                  }  
  38.                  ModelState.AddModelError("", msg);  
  39.                  return View(model);  
  40.              }  
  41.              catch (Exception e)  
  42.              {  
  43.                  ModelState.AddModelError("", e.Message);  
  44.                  return View(model);  
  45.              }  
  46.          }  
  47.    
  48.          public ActionResult Logout( )  
  49.          {  
  50.              string returnUrl = Request.Params["returnUrl"];  
  51.              returnUrl = returnUrl ?? Url.Action("Index""Home"new { area = "" });  
  52.              if (User.Identity.IsAuthenticated)  
  53.              {  
  54.                  AccountContract.Logout();  
  55.              }  
  56.              return Redirect(returnUrl);  
  57.          }  
  58.    
  59.          #endregion  
  60.      } 

MVC 视图:

  1. @model GMF.Demo.Site.Models.LoginModel  
  2. @{  
  3.     ViewBag.Title = "Login";  
  4.     Layout = "~/Views/Shared/_Layout.cshtml";  
  5. }  
  6. <h2>Login</h2>  
  7. @using (Html.BeginForm()) {  
  8.     @Html.AntiForgeryToken()  
  9.     @Html.ValidationSummary(true)  
  10.     <fieldset>  
  11.         <legend>LoginModel</legend>  
  12.         <div class="editor-label">  
  13.             @Html.LabelFor(model => model.Account)  
  14.         </div>  
  15.         <div class="editor-field">  
  16.             @Html.EditorFor(model => model.Account)  
  17.             @Html.ValidationMessageFor(model => model.Account)  
  18.         </div>  
  19.         <div class="editor-label">  
  20.             @Html.LabelFor(model => model.Password)  
  21.         </div>  
  22.         <div class="editor-field">  
  23.             @Html.EditorFor(model => model.Password)  
  24.             @Html.ValidationMessageFor(model => model.Password)  
  25.         </div>  
  26.         <div class="editor-label">  
  27.             @Html.LabelFor(model => model.IsRememberLogin)  
  28.         </div>  
  29.         <div class="editor-field">  
  30.             @Html.EditorFor(model => model.IsRememberLogin)  
  31.             @Html.ValidationMessageFor(model => model.IsRememberLogin)  
  32.         </div>  
  33.         @Html.HiddenFor(m => m.ReturnUrl)  
  34.         <p>  
  35.             <input type="submit" value="登录" />  
  36.         </p>  
  37.     </fieldset>  
  38. }  
  39. <div>  
  40.     @Html.ActionLink("Back to List""Index""Home")  
  41. </div>  
  42. @section Scripts {  
  43.     @Scripts.Render("~/bundles/jqueryval")  

至此,整个项目构架搭建完成,运行结果如下:
  

 

在本篇中,网站的Controller是依赖于站点业务实现与核心业务实现的,在下一篇中,将使用.net 4.0自带的MEF作为IOC对层与层之间的依赖进行解耦。

五、源码下载

GMFrameworkForBlog.zip

原文链接:http://www.cnblogs.com/guomingfeng/archive/2013/05/20/mvc-build.html

责任编辑:林师授 来源: 博客园
相关推荐

2010-03-10 15:19:06

Python源代码

2015-01-09 10:01:50

Spring MVC

2013-09-02 17:46:41

MVC架构设计MVC架构设计

2009-07-10 16:07:10

TikeSwing创建MVC体系结构

2010-08-30 09:35:19

2012-07-11 23:32:33

MVC3项目

2012-06-23 20:24:33

Web

2012-06-26 09:37:54

Web

2013-09-02 17:53:41

MVC架构设计MEF

2018-09-07 23:38:45

小程序开发框架

2014-01-09 18:32:27

IET需求导向网络下一代网络构架

2009-03-12 10:42:38

RoutingIgnoreRouteASP.NET

2022-02-24 20:34:02

Dooring开发桌面端应用

2021-05-17 09:27:07

项目实战优化项目构建时间

2017-11-10 11:59:54

开源NET高效

2023-11-01 07:24:55

2009-04-30 09:15:25

三层结构MVC架构

2019-07-31 10:18:17

Web 开发Python

2009-09-02 15:34:37

C#实现插件构架

2018-06-15 23:08:22

物联网系统构架互联网
点赞
收藏

51CTO技术栈公众号