| 〇、目錄一、前言 二、需求說明 (一) 功能返回值 (二) 實體基類 (一) 核心業(yè)務(wù)層 五、源碼獲取 一、前言在《上篇》中,已經(jīng)把項目整體結(jié)構(gòu)規(guī)劃做了個大概的規(guī)劃。在本文中,將使用代碼的方式來一一解說各個層次。由于要搭建一個基本完整的結(jié)構(gòu),可能文章會比較長。另外,本系列主要出于實用的目的,因而并不會嚴(yán)格按照傳統(tǒng)的三層那樣進(jìn)行非常明確的層次職能劃分。 二、需求說明在本系列中,為方便大家理解,將以一個賬戶管理的小系統(tǒng)來進(jìn)行解說,具體需求如下: 
 三、架構(gòu)基礎(chǔ)(一) 功能返回值對于一個操作性業(yè)務(wù)功能(比如添加,修改,刪除),通常我們處理返回值的做法是使用簡單類型,通常會有如下幾種方案: 
 這樣做有什么不妥之處呢,我們來逐一分析: 
 綜上,我們到底需要一個怎樣的業(yè)務(wù)操作結(jié)果呢? 
 再綜上,顯然簡單類型的返回值滿足不了需求了,那就需要定義一個專門用來封裝返回值信息的返回值類,這里定義如下: 1 /// <summary> 2 /// 業(yè)務(wù)操作結(jié)果信息類,對操作結(jié)果進(jìn)行封裝 3 /// </summary> 4 public class OperationResult 5 { 6 #region 構(gòu)造函數(shù) 7 8 /// <summary> 9 /// 初始化一個 業(yè)務(wù)操作結(jié)果信息類 的新實例 10 /// </summary> 11 /// <param name="resultType">業(yè)務(wù)操作結(jié)果類型</param> 12 public OperationResult(OperationResultType resultType) 13 { 14 ResultType = resultType; 15 } 16 17 /// <summary> 18 /// 初始化一個 定義返回消息的業(yè)務(wù)操作結(jié)果信息類 的新實例 19 /// </summary> 20 /// <param name="resultType">業(yè)務(wù)操作結(jié)果類型</param> 21 /// <param name="message">業(yè)務(wù)返回消息</param> 22 public OperationResult(OperationResultType resultType, string message) 23 : this(resultType) 24 { 25 Message = message; 26 } 27 28 /// <summary> 29 /// 初始化一個 定義返回消息與附加數(shù)據(jù)的業(yè)務(wù)操作結(jié)果信息類 的新實例 30 /// </summary> 31 /// <param name="resultType">業(yè)務(wù)操作結(jié)果類型</param> 32 /// <param name="message">業(yè)務(wù)返回消息</param> 33 /// <param name="appendData">業(yè)務(wù)返回數(shù)據(jù)</param> 34 public OperationResult(OperationResultType resultType, string message, object appendData) 35 : this(resultType, message) 36 { 37 AppendData = appendData; 38 } 39 40 /// <summary> 41 /// 初始化一個 定義返回消息與日志消息的業(yè)務(wù)操作結(jié)果信息類 的新實例 42 /// </summary> 43 /// <param name="resultType">業(yè)務(wù)操作結(jié)果類型</param> 44 /// <param name="message">業(yè)務(wù)返回消息</param> 45 /// <param name="logMessage">業(yè)務(wù)日志記錄消息</param> 46 public OperationResult(OperationResultType resultType, string message, string logMessage) 47 : this(resultType, message) 48 { 49 LogMessage = logMessage; 50 } 51 52 /// <summary> 53 /// 初始化一個 定義返回消息、日志消息與附加數(shù)據(jù)的業(yè)務(wù)操作結(jié)果信息類 的新實例 54 /// </summary> 55 /// <param name="resultType">業(yè)務(wù)操作結(jié)果類型</param> 56 /// <param name="message">業(yè)務(wù)返回消息</param> 57 /// <param name="logMessage">業(yè)務(wù)日志記錄消息</param> 58 /// <param name="appendData">業(yè)務(wù)返回數(shù)據(jù)</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 /// 獲取或設(shè)置 操作結(jié)果類型 71 /// </summary> 72 public OperationResultType ResultType { get; set; } 73 74 /// <summary> 75 /// 獲取或設(shè)置 操作返回信息 76 /// </summary> 77 public string Message { get; set; } 78 79 /// <summary> 80 /// 獲取或設(shè)置 操作返回的日志消息,用于記錄日志 81 /// </summary> 82 public string LogMessage { get; set; } 83 84 /// <summary> 85 /// 獲取或設(shè)置 操作結(jié)果附加信息 86 /// </summary> 87 public object AppendData { get; set; } 88 89 #endregion 90 } 再定義一個表示業(yè)務(wù)操作結(jié)果的枚舉,枚舉項上有一個DescriptionAttribute的特性,用來作為當(dāng)上面的Message為空時的返回結(jié)果描述。 1 /// <summary> 2 /// 表示業(yè)務(wù)操作結(jié)果的枚舉 3 /// </summary> 4 [Description("業(yè)務(wù)操作結(jié)果的枚舉")] 5 public enum OperationResultType 6 { 7 /// <summary> 8 /// 操作成功 9 /// </summary> 10 [Description("操作成功。")] 11 Success, 12 13 /// <summary> 14 /// 操作取消或操作沒引發(fā)任何變化 15 /// </summary> 16 [Description("操作沒有引發(fā)任何變化,提交取消。")] 17 NoChanged, 18 19 /// <summary> 20 /// 參數(shù)錯誤 21 /// </summary> 22 [Description("參數(shù)錯誤。")] 23 ParamError, 24 25 /// <summary> 26 /// 指定參數(shù)的數(shù)據(jù)不存在 27 /// </summary> 28 [Description("指定參數(shù)的數(shù)據(jù)不存在。")] 29 QueryNull, 30 31 /// <summary> 32 /// 權(quán)限不足 33 /// </summary> 34 [Description("當(dāng)前用戶權(quán)限不足,不能繼續(xù)操作。")] 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 /// 操作引發(fā)錯誤 51 /// </summary> 52 [Description("操作引發(fā)錯誤。")] 53 Error, 54 } (二) 實體基類對于業(yè)務(wù)實體,有一些相同的且必要的信息,比如信息的創(chuàng)建時間,總是必要的;再比如想讓數(shù)據(jù)庫有一個“回收站”的功能,以給數(shù)據(jù)刪除做個緩沖,或者很多數(shù)據(jù)并非想從數(shù)據(jù)庫中徹底刪除掉,只是暫時的“禁用”一下,添加個邏輯刪除的標(biāo)記也是必要的。再有就是想給所有實體數(shù)據(jù)倉儲操作來個類型限定,以防止傳入了其他非實體類型?;谝陨侠碛?,就有了下面這個實體基類: 1 /// <summary> 2 /// 可持久到數(shù)據(jù)庫的領(lǐng)域模型的基類。 3 /// </summary> 4 [Serializable] 5 public abstract class Entity 6 { 7 #region 構(gòu)造函數(shù) 8 9 /// <summary> 10 /// 數(shù)據(jù)實體基類 11 /// </summary> 12 protected Entity() 13 { 14 IsDeleted = false; 15 AddDate = DateTime.Now; 16 } 17 18 #endregion 19 20 #region 屬性 21 22 /// <summary> 23 /// 獲取或設(shè)置 獲取或設(shè)置是否禁用,邏輯上的刪除,非物理刪除 24 /// </summary> 25 public bool IsDeleted { get; set; } 26 27 /// <summary> 28 /// 獲取或設(shè)置 添加時間 29 /// </summary> 30 [DataType(DataType.DateTime)] 31 public DateTime AddDate { get; set; } 32 33 /// <summary> 34 /// 獲取或設(shè)置 版本控制標(biāo)識,用于處理并發(fā) 35 /// </summary> 36 [ConcurrencyCheck] 37 [Timestamp] 38 public byte[] Timestamp { get; set; } 39 40 #endregion 41 } 這里要補(bǔ)充一下,本來實體基類中是可以定義一個表示“實體編號”的Id屬性的,但有個問題,如果定義了,就限定了Id屬性的數(shù)據(jù)類型了,但實際需求中可能有些實體使用自增的int類型,有些實體使用的是易于數(shù)據(jù)合并的guid類型,因此為靈活方便,不在此限制住 Id的數(shù)據(jù)類型。 四、架構(gòu)分層  具體的架構(gòu)分層如下圖所示: (一) 核心業(yè)務(wù)層根據(jù) 需求說明 中定義的需求,簡單起見,這里只實現(xiàn)一個簡單的用戶登錄功能: 用戶信息實體: 1 /// <summary> 2 /// 實體類——用戶信息 3 /// </summary> 4 [Description("用戶信息")] 5 public class Member : Entity 6 { 7 /// <summary> 8 /// 獲取或設(shè)置 用戶編號 9 /// </summary> 10 public int Id { get; set; } 11 12 /// <summary> 13 /// 獲取或設(shè)置 用戶名 14 /// </summary> 15 [Required] 16 [StringLength(20)] 17 public string UserName { get; set; } 18 19 /// <summary> 20 /// 獲取或設(shè)置 密碼 21 /// </summary> 22 [Required] 23 [StringLength(32)] 24 public string Password { get; set; } 25 26 /// <summary> 27 /// 獲取或設(shè)置 用戶昵稱 28 /// </summary> 29 [Required] 30 [StringLength(20)] 31 public string NickName { get; set; } 32 33 /// <summary> 34 /// 獲取或設(shè)置 用戶郵箱 35 /// </summary> 36 [Required] 37 [StringLength(50)] 38 public string Email { get; set; } 39 40 /// <summary> 41 /// 獲取或設(shè)置 用戶擴(kuò)展信息 42 /// </summary> 43 public virtual MemberExtend Extend { get; set; } 44 45 /// <summary> 46 /// 獲取或設(shè)置 用戶擁有的角色信息集合 47 /// </summary> 48 public virtual ICollection<Role> Roles { get; set; } 49 50 /// <summary> 51 /// 獲取或設(shè)置 用戶登錄記錄集合 52 /// </summary> 53 public virtual ICollection<LoginLog> LoginLogs { get; set; } 54 } 核心業(yè)務(wù)契約:注意接口的返回值使用了上面定義的返回值類 1 /// <summary> 2 /// 賬戶模塊核心業(yè)務(wù)契約 3 /// </summary> 4 public interface IAccountContract 5 { 6 /// <summary> 7 /// 用戶登錄 8 /// </summary> 9 /// <param name="loginInfo">登錄信息</param> 10 /// <returns>業(yè)務(wù)操作結(jié)果</returns> 11 OperationResult Login(LoginInfo loginInfo); 12 } 核心業(yè)務(wù)實現(xiàn):核心業(yè)務(wù)實現(xiàn)類為抽象類,因沒有數(shù)據(jù)訪問功能,這里使用了一個Members字段來充當(dāng)數(shù)據(jù)源,業(yè)務(wù)功能的實現(xiàn)為虛方法,必要時可以在具體的客戶端(網(wǎng)站、桌面端,移動端)相應(yīng)的派生類中進(jìn)行重寫。請注意具體實現(xiàn)中對于返回值的處理。這里登錄只負(fù)責(zé)最核心的登錄業(yè)務(wù)操作,不涉及比如Http上下文狀態(tài)的操作。 1 /// <summary> 2 /// 賬戶模塊核心業(yè)務(wù)實現(xiàn) 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>業(yè)務(wù)操作結(jié)果</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 } (二) 站點(diǎn)業(yè)務(wù)層站點(diǎn)業(yè)務(wù)契約:站點(diǎn)業(yè)務(wù)契約繼承核心業(yè)務(wù)契約,即可擁有核心層定義的業(yè)務(wù)功能。站點(diǎn)登錄驗證使用了Forms的Cookie驗證,這里的退出不涉及核心層的操作,因而核心層沒有退出功能 1 /// <summary> 2 /// 賬戶模塊站點(diǎn)業(yè)務(wù)契約 3 /// </summary> 4 public interface IAccountSiteContract : IAccountContract 5 { 6 /// <summary> 7 /// 用戶登錄 8 /// </summary> 9 /// <param name="model">登錄模型信息</param> 10 /// <returns>業(yè)務(wù)操作結(jié)果</returns> 11 OperationResult Login(LoginModel model); 12 13 /// <summary> 14 /// 用戶退出 15 /// </summary> 16 void Logout(); 17 } 站點(diǎn)業(yè)務(wù)實現(xiàn):站點(diǎn)業(yè)務(wù)實現(xiàn)繼承核心業(yè)務(wù)實現(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ù)。 在這里需要注意的是,目前的項目中并沒有加入IOC組件來對層與層之間進(jìn)行解耦,在上層調(diào)用下層的時候,我們?nèi)匀灰匀缦路绞絹磉M(jìn)行實例化: 1 IAccountSiteContract accountContract = new AccountSiteService(); 這會造成層與層之間緊耦合,在后面的文章中,會加入.NET自帶的MEF組件進(jìn)行層之間的解耦,到時層對象實現(xiàn)化的工作將由MEF來完成,就需要把 AccountSiteService 類的可訪問性由 public 修改為 internal,以防止出現(xiàn)上面的實例化代碼出現(xiàn)。 1 /// <summary> 2 /// 賬戶模塊站點(diǎn)業(yè)務(wù)實現(xiàn) 3 /// </summary> 4 public class AccountSiteService : AccountService, IAccountSiteContract 5 { 6 /// <summary> 7 /// 用戶登錄 8 /// </summary> 9 /// <param name="model">登錄模型信息</param> 10 /// <returns>業(yè)務(wù)操作結(jié)果</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 } (三) 站點(diǎn)展現(xiàn)層MVC控制器:Action提供統(tǒng)一風(fēng)格的代碼來對業(yè)務(wù)操作結(jié)果OperationResult進(jìn)行處理 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 視圖: @model GMF.Demo.Site.Models.LoginModel
@{
    ViewBag.Title = "Login";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Login</h2>
@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òu)架搭建完成,運(yùn)行結(jié)果如下: 在本篇中,網(wǎng)站的Controller是依賴于站點(diǎn)業(yè)務(wù)實現(xiàn)與核心業(yè)務(wù)實現(xiàn)的,在下一篇中,將使用.net 4.0自帶的MEF作為IOC對層與層之間的依賴進(jìn)行解耦。 五、源碼下載為了讓大家能第一時間獲取到本架構(gòu)的最新代碼,也為了方便我對代碼的管理,本系列的源碼已加入微軟的開源項目網(wǎng)站 http://www.,地址為: 可以通過下列途徑獲取到最新代碼: 
 
 系列導(dǎo)航
 | 
|  |