您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)如何用MVC構(gòu)架進(jìn)行項(xiàng)目結(jié)構(gòu)搭建,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。
一、前言
下面將使用代碼的方式來(lái)一一解說(shuō)各個(gè)層次。由于要搭建一個(gè)基本完整的結(jié)構(gòu),可能文章會(huì)比較長(zhǎng)。另外出于實(shí)用的目的,因而并不會(huì)嚴(yán)格按照傳統(tǒng)的三層那樣進(jìn)行非常明確的層次職能劃分。
二、需求說(shuō)明
為方便大家理解,將以一個(gè)賬戶管理的小系統(tǒng)來(lái)進(jìn)行解說(shuō),具體需求如下:
用戶信息分主要信息與擴(kuò)展信息,一個(gè)用戶可以有(或沒(méi)有)一個(gè)用戶擴(kuò)展信息。
記錄用戶的登錄記錄,一個(gè)用戶可以有多條登錄記錄,但登錄記錄所屬用戶唯一。
一個(gè)用戶可以有多個(gè)角色,一個(gè)角色也可以分配給多個(gè)用戶。
三、架構(gòu)基礎(chǔ)
(一) 功能返回值
對(duì)于一個(gè)操作性業(yè)務(wù)功能(比如添加,修改,刪除),通常我們處理返回值的做法是使用簡(jiǎn)單類(lèi)型,通常會(huì)有如下幾種方案:
直接返回void,即什么也不返回,在操作過(guò)程中拋出異常,只要沒(méi)有異常拋出,就認(rèn)為是操作成功了
返回是否操作成功的bool類(lèi)型的返回值
返回操作變更后的新數(shù)據(jù)信息
返回表示各種結(jié)果的狀態(tài)碼的返回值
返回一個(gè)自定義枚舉來(lái)表示操作的各種結(jié)果
如果要返回多個(gè)值,還要使用 out 來(lái)添加返回參數(shù)
這樣做有什么不妥之處呢,我們來(lái)逐一分析:
靠拋異常的方式來(lái)終止系統(tǒng)的運(yùn)行,異常是沿調(diào)用堆棧逐層向上拋出的,會(huì)造成很大的性能問(wèn)題
bool值太死板,無(wú)法表示出業(yè)務(wù)操作中的各種情況
返回變更后的數(shù)據(jù),還要與原始數(shù)據(jù)來(lái)判斷才能得到是否操作成功
用狀態(tài)碼解決了2的問(wèn)題,但各種狀態(tài)碼的維護(hù)成本也會(huì)非常高
用枚舉值一定程序上解決了翻譯的問(wèn)題,但還是要把枚舉值翻譯成各種情況的文字描述
綜上,我們到底需要一個(gè)怎樣的業(yè)務(wù)操作結(jié)果呢?
要能表示操作的成功失?。◤U話)
要能快速表示各種操作場(chǎng)景(如參數(shù)錯(cuò)誤,查詢(xún)數(shù)據(jù)不存在,數(shù)據(jù)狀態(tài)不滿足操作要求等)
能返回附加的返回信息(如更新成功后有后續(xù)操作,需要使用更新后的新值)
最好在調(diào)用方能使用統(tǒng)一的代碼進(jìn)行返回值處理
最好能自定義返回的文字描述信息
最好能把返回給用戶的信息與日志記錄的信息分開(kāi)
再綜上,顯然簡(jiǎn)單類(lèi)型的返回值滿足不了需求了,那就需要定義一個(gè)專(zhuān)門(mén)用來(lái)封裝返回值信息的返回值類(lèi),這里定義如下:
/// <summary> /// 業(yè)務(wù)操作結(jié)果信息類(lèi),對(duì)操作結(jié)果進(jìn)行封裝 /// </summary> public class OperationResult { #region 構(gòu)造函數(shù) /// <summary> /// 初始化一個(gè) 業(yè)務(wù)操作結(jié)果信息類(lèi) 的新實(shí)例 /// </summary> /// <param name="resultType">業(yè)務(wù)操作結(jié)果類(lèi)型</param> public OperationResult(OperationResultType resultType) { ResultType = resultType; } /// <summary> /// 初始化一個(gè) 定義返回消息的業(yè)務(wù)操作結(jié)果信息類(lèi) 的新實(shí)例 /// </summary> /// <param name="resultType">業(yè)務(wù)操作結(jié)果類(lèi)型</param> /// <param name="message">業(yè)務(wù)返回消息</param> public OperationResult(OperationResultType resultType, string message) : this(resultType) { Message = message; } /// <summary> /// 初始化一個(gè) 定義返回消息與附加數(shù)據(jù)的業(yè)務(wù)操作結(jié)果信息類(lèi) 的新實(shí)例 /// </summary> /// <param name="resultType">業(yè)務(wù)操作結(jié)果類(lèi)型</param> /// <param name="message">業(yè)務(wù)返回消息</param> /// <param name="appendData">業(yè)務(wù)返回?cái)?shù)據(jù)</param> public OperationResult(OperationResultType resultType, string message, object appendData) : this(resultType, message) { AppendData = appendData; } /// <summary> /// 初始化一個(gè) 定義返回消息與日志消息的業(yè)務(wù)操作結(jié)果信息類(lèi) 的新實(shí)例 /// </summary> /// <param name="resultType">業(yè)務(wù)操作結(jié)果類(lèi)型</param> /// <param name="message">業(yè)務(wù)返回消息</param> /// <param name="logMessage">業(yè)務(wù)日志記錄消息</param> public OperationResult(OperationResultType resultType, string message, string logMessage) : this(resultType, message) { LogMessage = logMessage; } /// <summary> /// 初始化一個(gè) 定義返回消息、日志消息與附加數(shù)據(jù)的業(yè)務(wù)操作結(jié)果信息類(lèi) 的新實(shí)例 /// </summary> /// <param name="resultType">業(yè)務(wù)操作結(jié)果類(lèi)型</param> /// <param name="message">業(yè)務(wù)返回消息</param> /// <param name="logMessage">業(yè)務(wù)日志記錄消息</param> /// <param name="appendData">業(yè)務(wù)返回?cái)?shù)據(jù)</param> public OperationResult(OperationResultType resultType, string message, string logMessage, object appendData) : this(resultType, message, logMessage) { AppendData = appendData; } #endregion #region 屬性 /// <summary> /// 獲取或設(shè)置 操作結(jié)果類(lèi)型 /// </summary> public OperationResultType ResultType { get; set; } /// <summary> /// 獲取或設(shè)置 操作返回信息 /// </summary> public string Message { get; set; } /// <summary> /// 獲取或設(shè)置 操作返回的日志消息,用于記錄日志 /// </summary> public string LogMessage { get; set; } /// <summary> /// 獲取或設(shè)置 操作結(jié)果附加信息 /// </summary> public object AppendData { get; set; } #endregion }
再定義一個(gè)表示業(yè)務(wù)操作結(jié)果的枚舉,枚舉項(xiàng)上有一個(gè)DescriptionAttribute的特性,用來(lái)作為當(dāng)上面的Message為空時(shí)的返回結(jié)果描述。
/// <summary> /// 表示業(yè)務(wù)操作結(jié)果的枚舉 /// </summary> [Description("業(yè)務(wù)操作結(jié)果的枚舉")] public enum OperationResultType { /// <summary> /// 操作成功 /// </summary> [Description("操作成功。")] Success, /// <summary> /// 操作取消或操作沒(méi)引發(fā)任何變化 /// </summary> [Description("操作沒(méi)有引發(fā)任何變化,提交取消。")] NoChanged, /// <summary> /// 參數(shù)錯(cuò)誤 /// </summary> [Description("參數(shù)錯(cuò)誤。")] ParamError, /// <summary> /// 指定參數(shù)的數(shù)據(jù)不存在 /// </summary> [Description("指定參數(shù)的數(shù)據(jù)不存在。")] QueryNull, /// <summary> /// 權(quán)限不足 /// </summary> [Description("當(dāng)前用戶權(quán)限不足,不能繼續(xù)操作。")] PurviewLack, /// <summary> /// 非法操作 /// </summary> [Description("非法操作。")] IllegalOperation, /// <summary> /// 警告 /// </summary> [Description("警告")] Warning, /// <summary> /// 操作引發(fā)錯(cuò)誤 /// </summary> [Description("操作引發(fā)錯(cuò)誤。")] Error, }
(二) 實(shí)體基類(lèi)
對(duì)于業(yè)務(wù)實(shí)體,有一些相同的且必要的信息,比如信息的創(chuàng)建時(shí)間,總是必要的;再比如想讓數(shù)據(jù)庫(kù)有一個(gè)“回收站”的功能,以給數(shù)據(jù)刪除做個(gè)緩沖,或者很多數(shù)據(jù)并非想從數(shù)據(jù)庫(kù)中徹底刪除掉,只是暫時(shí)的“禁用”一下,添加個(gè)邏輯刪除的標(biāo)記也是必要的。再有就是想給所有實(shí)體數(shù)據(jù)倉(cāng)儲(chǔ)操作來(lái)個(gè)類(lèi)型限定,以防止傳入了其他非實(shí)體類(lèi)型?;谝陨侠碛桑陀辛讼旅孢@個(gè)實(shí)體基類(lèi):
/// <summary> /// 可持久到數(shù)據(jù)庫(kù)的領(lǐng)域模型的基類(lèi)。 /// </summary> [Serializable] public abstract class Entity { #region 構(gòu)造函數(shù) /// <summary> /// 數(shù)據(jù)實(shí)體基類(lèi) /// </summary> protected Entity() { IsDeleted = false; AddDate = DateTime.Now; } #endregion #region 屬性 /// <summary> /// 獲取或設(shè)置 獲取或設(shè)置是否禁用,邏輯上的刪除,非物理刪除 /// </summary> public bool IsDeleted { get; set; } /// <summary> /// 獲取或設(shè)置 添加時(shí)間 /// </summary> [DataType(DataType.DateTime)] public DateTime AddDate { get; set; } /// <summary> /// 獲取或設(shè)置 版本控制標(biāo)識(shí),用于處理并發(fā) /// </summary> [ConcurrencyCheck] 1621913756 public byte[] Timestamp { get; set; } #endregion }
這里要補(bǔ)充一下,本來(lái)實(shí)體基類(lèi)中是可以定義一個(gè)表示“實(shí)體編號(hào)”的Id屬性的,但有個(gè)問(wèn)題,如果定義了,就限定了Id屬性的數(shù)據(jù)類(lèi)型了,但實(shí)際需求中可能有些實(shí)體使用自增的int類(lèi)型,有些實(shí)體使用的是易于數(shù)據(jù)合并的guid類(lèi)型,因此為靈活方便,不在此限制住 Id的數(shù)據(jù)類(lèi)型。
四、架構(gòu)分層
具體的架構(gòu)分層如下圖所示:
(一) 核心業(yè)務(wù)層
根據(jù) 需求說(shuō)明 中定義的需求,簡(jiǎn)單起見(jiàn),這里只實(shí)現(xiàn)一個(gè)簡(jiǎn)單的用戶登錄功能:
用戶信息實(shí)體:
/// <summary> /// 實(shí)體類(lèi)——用戶信息 /// </summary> [Description("用戶信息")] public class Member : Entity { /// <summary> /// 獲取或設(shè)置 用戶編號(hào) /// </summary> public int Id { get; set; } /// <summary> /// 獲取或設(shè)置 用戶名 /// </summary> [Required] [StringLength(20)] public string UserName { get; set; } /// <summary> /// 獲取或設(shè)置 密碼 /// </summary> [Required] [StringLength(32)] public string Password { get; set; } /// <summary> /// 獲取或設(shè)置 用戶昵稱(chēng) /// </summary> [Required] [StringLength(20)] public string NickName { get; set; } /// <summary> /// 獲取或設(shè)置 用戶郵箱 /// </summary> [Required] [StringLength(50)] public string Email { get; set; } /// <summary> /// 獲取或設(shè)置 用戶擴(kuò)展信息 /// </summary> public virtual MemberExtend Extend { get; set; } /// <summary> /// 獲取或設(shè)置 用戶擁有的角色信息集合 /// </summary> public virtual ICollection<Role> Roles { get; set; } /// <summary> /// 獲取或設(shè)置 用戶登錄記錄集合 /// </summary> public virtual ICollection<LoginLog> LoginLogs { get; set; } }
核心業(yè)務(wù)契約:注意接口的返回值使用了上面定義的返回值類(lèi)
/// <summary> /// 賬戶模塊核心業(yè)務(wù)契約 /// </summary> public interface IAccountContract { /// <summary> /// 用戶登錄 /// </summary> /// <param name="loginInfo">登錄信息</param> /// <returns>業(yè)務(wù)操作結(jié)果</returns> OperationResult Login(LoginInfo loginInfo); }
核心業(yè)務(wù)實(shí)現(xiàn):核心業(yè)務(wù)實(shí)現(xiàn)類(lèi)為抽象類(lèi),因沒(méi)有數(shù)據(jù)訪問(wèn)功能,這里使用了一個(gè)Members字段來(lái)充當(dāng)數(shù)據(jù)源,業(yè)務(wù)功能的實(shí)現(xiàn)為虛方法,必要時(shí)可以在具體的客戶端(網(wǎng)站、桌面端,移動(dòng)端)相應(yīng)的派生類(lèi)中進(jìn)行重寫(xiě)。請(qǐng)注意具體實(shí)現(xiàn)中對(duì)于返回值的處理。這里登錄只負(fù)責(zé)最核心的登錄業(yè)務(wù)操作,不涉及比如Http上下文狀態(tài)的操作。
/// <summary> /// 賬戶模塊核心業(yè)務(wù)實(shí)現(xiàn) /// </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>業(yè)務(wù)操作結(jié)果</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, "指定賬號(hào)的用戶不存在。"); } 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); } }
(二) 站點(diǎn)業(yè)務(wù)層
站點(diǎn)業(yè)務(wù)契約:站點(diǎn)業(yè)務(wù)契約繼承核心業(yè)務(wù)契約,即可擁有核心層定義的業(yè)務(wù)功能。站點(diǎn)登錄驗(yàn)證使用了Forms的Cookie驗(yàn)證,這里的退出不涉及核心層的操作,因而核心層沒(méi)有退出功能
/// <summary> /// 賬戶模塊站點(diǎn)業(yè)務(wù)契約 /// </summary> public interface IAccountSiteContract : IAccountContract { /// <summary> /// 用戶登錄 /// </summary> /// <param name="model">登錄模型信息</param> /// <returns>業(yè)務(wù)操作結(jié)果</returns> OperationResult Login(LoginModel model); /// <summary> /// 用戶退出 /// </summary> void Logout(); }
站點(diǎn)業(yè)務(wù)實(shí)現(xiàn):站點(diǎn)業(yè)務(wù)實(shí)現(xiàn)繼承核心業(yè)務(wù)實(shí)現(xiàn)與站點(diǎn)業(yè)務(wù)契約,負(fù)責(zé)把從UI中接收到的視圖模型信息轉(zhuǎn)換為符合核心層定義的參數(shù),并處理與網(wǎng)站狀態(tài)相關(guān)的Session,Cookie等Http相關(guān)業(yè)務(wù)
/// <summary> /// 賬戶模塊站點(diǎn)業(yè)務(wù)實(shí)現(xiàn) /// </summary> public class AccountSiteService : AccountService, IAccountSiteContract { /// <summary> /// 用戶登錄 /// </summary> /// <param name="model">登錄模型信息</param> /// <returns>業(yè)務(wù)操作結(jié)果</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(); } }
(三) 站點(diǎn)展現(xiàn)層
MVC控制器:Action提供統(tǒng)一風(fēng)格的代碼來(lái)對(duì)業(yè)務(wù)操作結(jié)果OperationResult進(jìn)行處理
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") }
至此,整個(gè)項(xiàng)目構(gòu)架搭建完成,運(yùn)行結(jié)果如下:
以上就是如何用MVC構(gòu)架進(jìn)行項(xiàng)目結(jié)構(gòu)搭建,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。