本篇文章给大家分享的是有关如何用MVC构架进行项目结构搭建,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。
一、前言
下面将使用代码的方式来一一解说各个层次。由于要搭建一个基本完整的结构,可能文章会比较长。另外出于实用的目的,因而并不会严格按照传统的三层那样进行非常明确的层次职能划分。
二、需求说明
为方便大家理解,将以一个账户管理的小系统来进行解说,具体需求如下:
用户信息分主要信息与扩展信息,一个用户可以有(或没有)一个用户扩展信息。
记录用户的登录记录,一个用户可以有多条登录记录,但登录记录所属用户唯一。
一个用户可以有多个角色,一个角色也可以分配给多个用户。
三、架构基础
(一) 功能返回值
对于一个操作性业务功能(比如添加,修改,删除),通常我们处理返回值的做法是使用简单类型,通常会有如下几种方案:
直接返回void,即什么也不返回,在操作过程中抛出异常,只要没有异常抛出,就认为是操作成功了
返回是否操作成功的bool类型的返回值
返回操作变更后的新数据信息
返回表示各种结果的状态码的返回值
返回一个自定义枚举来表示操作的各种结果
如果要返回多个值,还要使用 out 来添加返回参数
这样做有什么不妥之处呢,我们来逐一分析:
靠抛异常的方式来终止系统的运行,异常是沿调用堆栈逐层向上抛出的,会造成很大的性能问题
bool值太死板,无法表示出业务操作中的各种情况
返回变更后的数据,还要与原始数据来判断才能得到是否操作成功
用状态码解决了2的问题,但各种状态码的维护成本也会非常高
用枚举值一定程序上解决了翻译的问题,但还是要把枚举值翻译成各种情况的文字描述
综上,我们到底需要一个怎样的业务操作结果呢?
要能表示操作的成功失败(废话)
要能快速表示各种操作场景(如参数错误,查询数据不存在,数据状态不满足操作要求等)
能返回附加的返回信息(如更新成功后有后续操作,需要使用更新后的新值)
最好在调用方能使用统一的代码进行返回值处理
最好能自定义返回的文字描述信息
最好能把返回给用户的信息与日志记录的信息分开
再综上,显然简单类型的返回值满足不了需求了,那就需要定义一个专门用来封装返回值信息的返回值类,这里定义如下:
/// <summary> /// 业务操作结果信息类,对操作结果进行封装 /// </summary> public class OperationResult { #region 构造函数 /// <summary> /// 初始化一个 业务操作结果信息类 的新实例 /// </summary> /// <param name="resultType">业务操作结果类型</param> public OperationResult(OperationResultType resultType) { ResultType = resultType; } /// <summary> /// 初始化一个 定义返回消息的业务操作结果信息类 的新实例 /// </summary> /// <param name="resultType">业务操作结果类型</param> /// <param name="message">业务返回消息</param> public OperationResult(OperationResultType resultType, string message) : this(resultType) { Message = message; } /// <summary> /// 初始化一个 定义返回消息与附加数据的业务操作结果信息类 的新实例 /// </summary> /// <param name="resultType">业务操作结果类型</param> /// <param name="message">业务返回消息</param> /// <param name="appendData">业务返回数据</param> public OperationResult(OperationResultType resultType, string message, object appendData) : this(resultType, message) { AppendData = appendData; } /// <summary> /// 初始化一个 定义返回消息与日志消息的业务操作结果信息类 的新实例 /// </summary> /// <param name="resultType">业务操作结果类型</param> /// <param name="message">业务返回消息</param> /// <param name="logMessage">业务日志记录消息</param> public OperationResult(OperationResultType resultType, string message, string logMessage) : this(resultType, message) { LogMessage = logMessage; } /// <summary> /// 初始化一个 定义返回消息、日志消息与附加数据的业务操作结果信息类 的新实例 /// </summary> /// <param name="resultType">业务操作结果类型</param> /// <param name="message">业务返回消息</param> /// <param name="logMessage">业务日志记录消息</param> /// <param name="appendData">业务返回数据</param> public OperationResult(OperationResultType resultType, string message, string logMessage, object appendData) : this(resultType, message, logMessage) { AppendData = appendData; } #endregion #region 属性 /// <summary> /// 获取或设置 操作结果类型 /// </summary> public OperationResultType ResultType { get; set; } /// <summary> /// 获取或设置 操作返回信息 /// </summary> public string Message { get; set; } /// <summary> /// 获取或设置 操作返回的日志消息,用于记录日志 /// </summary> public string LogMessage { get; set; } /// <summary> /// 获取或设置 操作结果附加信息 /// </summary> public object AppendData { get; set; } #endregion }
再定义一个表示业务操作结果的枚举,枚举项上有一个DescriptionAttribute的特性,用来作为当上面的Message为空时的返回结果描述。
/// <summary> /// 表示业务操作结果的枚举 /// </summary> [Description("业务操作结果的枚举")] public enum OperationResultType { /// <summary> /// 操作成功 /// </summary> [Description("操作成功。")] Success, /// <summary> /// 操作取消或操作没引发任何变化 /// </summary> [Description("操作没有引发任何变化,提交取消。")] NoChanged, /// <summary> /// 参数错误 /// </summary> [Description("参数错误。")] ParamError, /// <summary> /// 指定参数的数据不存在 /// </summary> [Description("指定参数的数据不存在。")] QueryNull, /// <summary> /// 权限不足 /// </summary> [Description("当前用户权限不足,不能继续操作。")] PurviewLack, /// <summary> /// 非法操作 /// </summary> [Description("非法操作。")] IllegalOperation, /// <summary> /// 警告 /// </summary> [Description("警告")] Warning, /// <summary> /// 操作引发错误 /// </summary> [Description("操作引发错误。")] Error, }
(二) 实体基类
对于业务实体,有一些相同的且必要的信息,比如信息的创建时间,总是必要的;再比如想让数据库有一个“回收站”的功能,以给数据删除做个缓冲,或者很多数据并非想从数据库中彻底删除掉,只是暂时的“禁用”一下,添加个逻辑删除的标记也是必要的。再有就是想给所有实体数据仓储操作来个类型限定,以防止传入了其他非实体类型。基于以上理由,就有了下面这个实体基类:
/// <summary> /// 可持久到数据库的领域模型的基类。 /// </summary> [Serializable] public abstract class Entity { #region 构造函数 /// <summary> /// 数据实体基类 /// </summary> protected Entity() { IsDeleted = false; AddDate = DateTime.Now; } #endregion #region 属性 /// <summary> /// 获取或设置 获取或设置是否禁用,逻辑上的删除,非物理删除 /// </summary> public bool IsDeleted { get; set; } /// <summary> /// 获取或设置 添加时间 /// </summary> [DataType(DataType.DateTime)] public DateTime AddDate { get; set; } /// <summary> /// 获取或设置 版本控制标识,用于处理并发 /// </summary> [ConcurrencyCheck] 1621913756 public byte[] Timestamp { get; set; } #endregion }
这里要补充一下,本来实体基类中是可以定义一个表示“实体编号”的Id属性的,但有个问题,如果定义了,就限定了Id属性的数据类型了,但实际需求中可能有些实体使用自增的int类型,有些实体使用的是易于数据合并的guid类型,因此为灵活方便,不在此限制住 Id的数据类型。
四、架构分层
具体的架构分层如下图所示:
(一) 核心业务层
根据 需求说明 中定义的需求,简单起见,这里只实现一个简单的用户登录功能:
用户信息实体:
/// <summary> /// 实体类——用户信息 /// </summary> [Description("用户信息")] public class Member : Entity { /// <summary> /// 获取或设置 用户编号 /// </summary> public int Id { get; set; } /// <summary> /// 获取或设置 用户名 /// </summary> [Required] [StringLength(20)] public string UserName { get; set; } /// <summary> /// 获取或设置 密码 /// </summary> [Required] [StringLength(32)] public string Password { get; set; } /// <summary> /// 获取或设置 用户昵称 /// </summary> [Required] [StringLength(20)] public string NickName { get; set; } /// <summary> /// 获取或设置 用户邮箱 /// </summary> [Required] [StringLength(50)] public string Email { get; set; } /// <summary> /// 获取或设置 用户扩展信息 /// </summary> public virtual MemberExtend Extend { get; set; } /// <summary> /// 获取或设置 用户拥有的角色信息集合 /// </summary> public virtual ICollection<Role> Roles { get; set; } /// <summary> /// 获取或设置 用户登录记录集合 /// </summary> public virtual ICollection<LoginLog> LoginLogs { get; set; } }
核心业务契约:注意接口的返回值使用了上面定义的返回值类
/// <summary> /// 账户模块核心业务契约 /// </summary> public interface IAccountContract { /// <summary> /// 用户登录 /// </summary> /// <param name="loginInfo">登录信息</param> /// <returns>业务操作结果</returns> OperationResult Login(LoginInfo loginInfo); }
核心业务实现:核心业务实现类为抽象类,因没有数据访问功能,这里使用了一个Members字段来充当数据源,业务功能的实现为虚方法,必要时可以在具体的客户端(网站、桌面端,移动端)相应的派生类中进行重写。请注意具体实现中对于返回值的处理。这里登录只负责最核心的登录业务操作,不涉及比如Http上下文状态的操作。
/// <summary> /// 账户模块核心业务实现 /// </summary> public abstract class AccountService : IAccountContract { private static readonly Member[] Members = new[] { new Member { UserName = "admin", Password = "123456", Email = "admin@gmfcn.net", NickName = "管理员" }, new Member { UserName = "gmfcn", Password = "123456", Email = "mf.guo@qq.com", NickName = "郭明锋" } }; private static readonly List<LoginLog> LoginLogs = new List<LoginLog>(); /// <summary> /// 用户登录 /// </summary> /// <param name="loginInfo">登录信息</param> /// <returns>业务操作结果</returns> public virtual OperationResult Login(LoginInfo loginInfo) { PublicHelper.CheckArgument(loginInfo, "loginInfo"); Member member = Members.SingleOrDefault(m => m.UserName == loginInfo.Access || m.Email == loginInfo.Access); if (member == null) { return new OperationResult(OperationResultType.QueryNull, "指定账号的用户不存在。"); } if (member.Password != loginInfo.Password) { return new OperationResult(OperationResultType.Warning, "登录密码不正确。"); } LoginLog loginLog = new LoginLog { IpAddress = loginInfo.IpAddress, Member = member }; LoginLogs.Add(loginLog); return new OperationResult(OperationResultType.Success, "登录成功。", member); } }
(二) 站点业务层
站点业务契约:站点业务契约继承核心业务契约,即可拥有核心层定义的业务功能。站点登录验证使用了Forms的Cookie验证,这里的退出不涉及核心层的操作,因而核心层没有退出功能
/// <summary> /// 账户模块站点业务契约 /// </summary> public interface IAccountSiteContract : IAccountContract { /// <summary> /// 用户登录 /// </summary> /// <param name="model">登录模型信息</param> /// <returns>业务操作结果</returns> OperationResult Login(LoginModel model); /// <summary> /// 用户退出 /// </summary> void Logout(); }
站点业务实现:站点业务实现继承核心业务实现与站点业务契约,负责把从UI中接收到的视图模型信息转换为符合核心层定义的参数,并处理与网站状态相关的Session,Cookie等Http相关业务
/// <summary> /// 账户模块站点业务实现 /// </summary> public class AccountSiteService : AccountService, IAccountSiteContract { /// <summary> /// 用户登录 /// </summary> /// <param name="model">登录模型信息</param> /// <returns>业务操作结果</returns> public OperationResult Login(LoginModel model) { PublicHelper.CheckArgument(model, "model"); LoginInfo loginInfo = new LoginInfo { Access = model.Account, Password = model.Password, IpAddress = HttpContext.Current.Request.UserHostAddress }; OperationResult result = base.Login(loginInfo); if (result.ResultType == OperationResultType.Success) { Member member = (Member)result.AppendData; DateTime expiration = model.IsRememberLogin ? DateTime.Now.AddDays(7) : DateTime.Now.Add(FormsAuthentication.Timeout); FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, member.UserName, DateTime.Now, expiration, true, member.NickName, FormsAuthentication.FormsCookiePath); HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket)); if (model.IsRememberLogin) { cookie.Expires = DateTime.Now.AddDays(7); } HttpContext.Current.Response.Cookies.Set(cookie); result.AppendData = null; } return result; } /// <summary> /// 用户退出 /// </summary> public void Logout() { FormsAuthentication.SignOut(); } }
(三) 站点展现层
MVC控制器:Action提供统一风格的代码来对业务操作结果OperationResult进行处理
public class AccountController : Controller { public AccountController() { AccountContract = new AccountSiteService(); } #region 属性 public IAccountSiteContract AccountContract { get; set; } #endregion #region 视图功能 public ActionResult Login() { string returnUrl = Request.Params["returnUrl"]; returnUrl = returnUrl ?? Url.Action("Index", "Home", new { area = "" }); LoginModel model = new LoginModel { ReturnUrl = returnUrl }; return View(model); } [HttpPost] public ActionResult Login(LoginModel model) { try { OperationResult result = AccountContract.Login(model); string msg = result.Message ?? result.ResultType.ToDescription(); if (result.ResultType == OperationResultType.Success) { return Redirect(model.ReturnUrl); } ModelState.AddModelError("", msg); return View(model); } catch (Exception e) { ModelState.AddModelError("", e.Message); return View(model); } } public ActionResult Logout( ) { string returnUrl = Request.Params["returnUrl"]; returnUrl = returnUrl ?? Url.Action("Index", "Home", new { area = "" }); if (User.Identity.IsAuthenticated) { AccountContract.Logout(); } return Redirect(returnUrl); } #endregion }
MVC 视图:
@model GMF.Demo.Site.Models.LoginModel @{ ViewBag.Title = "Login"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h3>Login</h3> @using (Html.BeginForm()) { @Html.AntiForgeryToken() @Html.ValidationSummary(true) <fieldset> <legend>LoginModel</legend> <div class="editor-label"> @Html.LabelFor(model => model.Account) </div> <div class="editor-field"> @Html.EditorFor(model => model.Account) @Html.ValidationMessageFor(model => model.Account) </div> <div class="editor-label"> @Html.LabelFor(model => model.Password) </div> <div class="editor-field"> @Html.EditorFor(model => model.Password) @Html.ValidationMessageFor(model => model.Password) </div> <div class="editor-label"> @Html.LabelFor(model => model.IsRememberLogin) </div> <div class="editor-field"> @Html.EditorFor(model => model.IsRememberLogin) @Html.ValidationMessageFor(model => model.IsRememberLogin) </div> @Html.HiddenFor(m => m.ReturnUrl) <p> <input type="submit" value="登录" /> </p> </fieldset> } <div> @Html.ActionLink("Back to List", "Index", "Home") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }
至此,整个项目构架搭建完成,运行结果如下:
以上就是如何用MVC构架进行项目结构搭建,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。