Part 3: 設計邏輯層:核心開發
如前所述,我們的解決方案如下所示:
下面我們討論整個應用的結構,根據應用中不同組件的邏輯相關性,分離到不同的層中,層與層之間的通訊通過或者不通過限制。分層屬於架構風格,在應用的長時間生命周期中,解決維護和擴展問題。
所以,讓我們在解決方案中添加一個類庫項目,命名為 Application.Common.
Application.Common :
這是一個類庫項目, 提供公共功能,可以被不同的業務邏輯層使用。例如:安全,日志,跟蹤,驗證等等. 定義在這個層中的組件,不僅可以被不同的層使用,還可以在不同的應用中使用。為了未來容易使用,我們使用依賴注入和抽象,在應用中實現最小化的修改。
例如,在我們馬上用到的,驗證組件用來驗證數據,定制的日志器來記錄錯誤或者警告。
在添加了 Common 類庫之后,解決方案的文件夾如下所示:
Application.Core:
這個層實現系統的核心處理邏輯,封裝相關的所有業務邏輯。從基本上說,這個層通常實現領域處理的邏輯。這個層還經常通過核心層的工作單元,以便完成 PI 特性,主要的目標是明確區分和分離核心領域的邏輯與基礎架構的具體細節,例如,數據訪問和數據倉儲的具體技術,像 ORM ,或者簡單的數據訪問庫,或者面向方面的架構等等。通過分離系統的核心功能,我們就可以進一步增強系統的可維護性,甚至可以替換底層的組件,而很少影響到整個應用。
下一步,我們將在解決方案中添加名為 Application.DAL 的類庫。
Application.DAL:
DAL 的職責是提供數據訪問和數據的持久化處理;維護多個會話,連接到不同的數據庫等等。這里的主要目標是通過接口和約定包裝 EF 數據訪問上下文對象,使得核心層不會直接依賴 EF。數據持久化組件提供駐留在系統內的數據訪問,也提供系統外的數據訪問。比如對外部系統提供服務的 Web 服務。因此,這里既包含類似 倉儲模式的機制來支持對系統內的數據訪問,還提供服務代理來使用其它外部系統通過 Web 服務提供的數據,另外,層中還提供可以對所有倉儲使用的基類和組件。
下一步,我們將會在解決方案中添加一個名為 Application.Repository 的類庫。
Application.Repository:
這個類庫僅僅可以通過 Application.Manager 訪問,對於領域中的每一個根實體對象,我們需要創建一個倉儲,基本上,所謂的倉儲就是封裝了訪問應用數據的處理邏輯的類和組件。而且,它們處理數據處理的核心功能,使得整個應用有更好的可維護性,並且可以在 Manager 和 Core 之間進行解耦。
下一步,我們需要創建名為 Application.DTO 類庫。
Application.DTO:
還是一個類庫,包含了與實體不同的數據類,其中僅有表示數據的屬性,但是沒有處理數據的方法,用來在表示層 Applicaiton.Web 和服務層 Application.Manager 之間進行通訊。數據傳輸對象是用來封裝數據的對象,用來從系統的一個子系統將數據傳遞到另外一個子系統。這里我們使用 DTO 對象在 Manager 層和 UI 層之間傳遞數據。使用 DTO 的主要優點是可以在分布式的系統中減少數據的流量,在 MVC 模式中,也是重要的一個部分。我們也可以為方法的調用來封裝數據,在方法包含4,5個以上的參數的時候,經常使用 DTO 來傳遞參數。
下一步,我們創建 Application.Manager 類庫。
Application.Manager :
這個類庫僅僅被 Applicaiton.Web 訪問,對於我們在 Manager 中定義的每個模塊,Manager 的主要職責是接受來自 UI 的請求,將數據傳遞到倉儲中的領域對象中進行處理,將處理結果返回到界面層,這個層是 UI 層和倉儲層之間的中間層。
Application.Web:
在前面的文章中,我們已經使用 javascript 中的模擬數據實現過該層。這里並不僅僅依賴於 ASP.NET MVC,界面可以包含用戶的界面組件,像 HTML,.aspx, cshtml,MVC 等等,它也可以是是任何的 Windows 應用。這里通過方法與 Manager 層通訊,封裝返回的結果,選擇將錯誤信息顯示在頁面1 或者頁面2 中。這個層使用 javascript 來家在表示層的模型,但實際的數據處理通過 ajax 請求發送到服務器處理,所以,服務器負責處理業務立即。而表示層處理表示邏輯問題。
要理解各層之間通訊的最好方式,就是讓我們重溫一下初始的需求。
Screen 1: 聯系人列表 - 顯示所有聯系人
1.1 界面需要顯示數據庫中所有的聯系人信息.
1.2 用戶可以刪除聯系人.
1.3 用戶可以編輯聯系人信息.
1.4 用戶可以創建新的聯系人.
為了填充表格中的數據,在頁面加載的時候,我們調用 ContactController 的 GetAllProfiles() 方法,這個方法返回數據庫中所有的聯系人信息,然后以 JSON 的形式返回到頁面,我們將數據以 self.Profiles 的形式綁定到一個 javascript 對象,下面就是 contact.js 代碼中 GetAllProfiles() 方法的定義。
var ProfilesViewModel = function () { var self = this; var url = "/contact/GetAllProfiles"; var refresh = function () { $.getJSON(url, {}, function (data) { self.Profiles(data); }); };
在點擊刪除按鈕的時候,我們調用 ContactController 的 GetAllProfiles() 方法,這個方法從數據庫中刪除聯系人信息。下面是 contact.js 中 DeleteProfile() 方法的定義。
self.removeProfile = function (profile) { if (confirm("Are you sure you want to delete this profile?")) { var id = profile.ProfileId; waitingDialog({}); $.ajax({ type: 'DELETE', url: 'Contact/DeleteProfile/' + id, success: function () { self.Profiles.remove(profile); }, error: function (err) { var error = JSON.parse(err.responseText); $("<div></div>").html(error.Message).dialog({ modal: true, title: "Error", buttons: { "Ok": function () { $(this).dialog("close"); } } }).show(); }, complete: function () { closeWaitingDialog(); } }); } };
對於創建和編輯按鈕來說,我們僅僅重定向到 CreateEdit 頁面,如果 id 參數是 0 表示創建新聯系人,對於編輯來說,id 就是編輯的聯系人編號了。下面的代碼就是 contact.js 中 的 createProfile 和 editProfile 方法
self.createProfile = function () { window.location.href = '/Contact/CreateEdit/0'; }; self.editProfile = function (profile) { window.location.href = '/Contact/CreateEdit/' + profile.ProfileId; };
下面的圖展示了主要的三個層之間的關系。
Screen 2: 創建新聯系人
界面提供一個空白的聯系人界面,並且提供一下功能.
2.1 用戶可以輸入用戶的姓,名,郵件地址
2.2 通過點擊添加號碼按鈕,允許添加任何個電話號碼
2.3 用戶可以刪除任意電話號碼.
2.4 通過點擊添加地址按鈕,允許添加任意多個地址.
2.5 用戶可以刪除任意的地址.
2.6 點擊保存按鈕,可以保存用戶輸入的所有信息到數據庫中,然后回到聯系人列表頁面.
2.7 點擊返回按鈕,可以回到聯系人列表.
Screen 3: 更新聯系人信息
這個界面顯示聯系人的詳細信息.
3.1 用戶可以編輯聯系人的姓,名,郵件地址.
3.2 用戶可以通過點擊添加,刪除號碼按鈕來添加,刪除用戶的電話號碼.
3.3 用戶可以通過點擊添加,刪除地址按鈕來添加,刪除用戶的地址。
3.4 點擊保存可以將用戶的詳細信息更新到數據庫中,然后返回聯系人列表
3.5 點擊返回按鈕可以回到聯系人列表
如前面的實現所見,創建和編輯的需求使用的是同一個頁面 CreateEdit.cshtml,通過 profileId 來進行區分,如果 profileId 是 0,表示新建,否則,就是編輯存在的信息,下面是實現的細節。
在任何情況下,在頁面加載和初始化 PhoneType 和 AddressType 的時候,在 ContactController 控制器的 InitializePageData() 方法中,在 CreateEdit.js 中的下列代碼初始化數組。
var AddressTypeData; var PhoneTypeData; $.ajax({ url: urlContact + '/InitializePageData', async: false, dataType: 'json', success: function (json) { AddressTypeData = json.lstAddressTypeDTO; PhoneTypeData = json.lstPhoneTypeDTO; } });
然后,對於編輯聯系人信息來說,我們需要獲取詳細信息,通過 ContactController 中的GetProfileById() 方法實現,我們修改 CreateEdit.js 。
$.ajax({ url: urlContact + '/GetProfileById/' + profileId, async: false, dataType: 'json', success: function (json) { self.profile = ko.observable(new Profile(json)); self.phoneNumbers = ko.observableArray(ko.utils.arrayMap(json.PhoneDTO, function (phone) { return phone; })); self.addresses = ko.observableArray(ko.utils.arrayMap(json.AddressDTO, function (address) { return address; })); } });
最后,我們使用兩個方法保存數據,如果我們是創建新的聯系人,調用 ContactController 的 SaveProfileInformtion() 方法,否則調用 UpdateProfileInformation() 方法,我們將 CreateEdit.js 修改如下。
$.ajax({ type: (self.profile().ProfileId > 0 ? 'PUT' : 'POST'), cache: false, dataType: 'json', url: urlContact + (self.profile().ProfileId > 0 ? '/UpdateProfileInformation?id=' + self.profile().ProfileId : '/SaveProfileInformation'), data: JSON.stringify(ko.toJS(self.profile())), contentType: 'application/json; charset=utf-8', async: false, success: function (data) { window.location.href = '/contact'; }, error: function (err) { var err = JSON.parse(err.responseText); var errors = ""; for (var key in err) { if (err.hasOwnProperty(key)) { errors += key.replace("profile.", "") + " : " + err[key]; } } $("<div></div>").html(errors).dialog({ modal: true, title: JSON.parse(err.responseText).Message, buttons: { "Ok": function () { $(this).dialog("close"); } } }).show(); }, complete: function () { } });
總結
就是這樣,希望你喜歡這篇文章,我不是一個專家,我希望你享受這個系列並且能夠學到些什么。
如果有任何問題歡迎進行討論,謝謝。
How to use code
從這里可以下載數據庫的腳本:
Application_DB.sql
在 VS 中運行程序,需要啟用 Allow NuGet to download missing packages during build ,
或者看下面的鏈接說明。
http://docs.nuget.org/docs/workflows/using-nuget-without-committing-packages
最后,修改你的 Application.Web 項目中的數據庫連接串。
參考資料
- http://knockoutjs.com/
- https://github.com/ericmbarnard/Knockout-Validation/wiki/Configuration
- http://twitter.github.com/bootstrap/
- http://docs.castleproject.org/Windsor.MainPage.ashx
- http://microsoftnlayerapp.codeplex.com/
- http://msdn.microsoft.com/en-us/library/ff921348.aspx
Last edited Jan 18 at 6:41 AM by anandranjanpandey, version 5