再簡單的功能,也需要一坨代碼的支持。Profile 的編輯功能主要就是修改個人的信息。比如用戶名、頭像、性別、電話……雖然只是一個編輯界面,但添加下來,涉及了6個文件的修改和7個新創建的文件。各種生成的和手寫的代碼,共有934行之多。 1. Account 和 Profile 分離 什麼是 Acco ...
再簡單的功能,也需要一坨代碼的支持。Profile 的編輯功能主要就是修改個人的信息。比如用戶名、頭像、性別、電話……雖然只是一個編輯界面,但添加下來,涉及了6個文件的修改和7個新創建的文件。各種生成的和手寫的代碼,共有934行之多。
1. Account 和 Profile 分離
什麼是 Account?通常這個詞被翻譯成“帳戶”。我的理解是,這個 Model 里的內容,是為了登錄而設計的。而 Profile 呢,應該保存那些和登錄無關的附加信息,比如昵稱、頭像之類的。不過,有一點坑的是,ASP.NET Core 預設的那個 Identify 服務把電話號碼也做為 Account 的一部分,但沒有提供通過電話來查找一個 User 的方法。雖然可以通過寫一個 Extension 來實現這個功能,暫時先放在一邊,有需求再說。
在原來的“悟空CRM”中,所有的信息都是放在 user 這個表裡的。對於 CRM 這個應用,也不是一個大的問題。但我認為還是應該把 Profile 和 Account 分開。這樣,在登錄及驗證用戶許可權的時候,就不需要去訪問不相關的 Profile 里的內容。也許有朋友會說:我通過 SELECT 那些我需要的列,不也一樣可以實現這個結果嗎?但實際上,一個 Row 的內容,都是保存在一起的。只是資料庫幫你過濾出來那些需要的信息。但在讀取的時候,還是要去訪問一整個 Row 的內容。如果分開當然就完全不需要訪問了(雖然還有個 ID 在那裡,但一個 ID 最多才4個位元組)。
2. Repository
Repository 模式其實算是對一些使用 Plain Object 做 ORM 的系統的補充。按照 MVC 的實踐準則,業務邏輯應該是寫在 Model 這一層的。但為了保持 Model 是一個 Plain Object,只能再引入一層抽象,用來嫁接 Controller 和底層的 Model。當然,使用 Repository 還有另外一個好處,就是把資料庫隔離開了。這樣,對於 Controller 的單元測試就方便多了。否則,對 Controller 里一些邏輯的測試,還需要配置資料庫。不過,有得就一定會有失。這裡雖然測試 Controller 方便了,確還得對 Repository 本身時行測試。比較幸運的是,Entity Framework Core 提供了一個 InMemory 的資料庫,專門用來測試對資料庫的訪問。
通常 Repository 會包含以下這些方法:
IEnumerable<Model> GetInstances();
Model GetModelByID(int ID);
void InsertModel(Model model);
void DeleteModel(int ID);
void UpdateModel(Model model);
void Save();
當然,對於具體的某個哪個 Model,這個列表也可以做一些修改。比如對於 Profile 這個 Model,我只使用了:
Task<Profile> GetCurrentUserProfileAsync(ClaimsPrincipal userClaimsPrincipal);
Task<ProfileViewModel> GetCurrentUserProfileViewModelAsync(ClaimsPrincipal userClaimsPrincipal);
Task UpdateProfileAsync(ClaimsPrincipal userClaimsPrincipal, ProfileViewModel model);
感覺上,這三個函數應該做一些修改。因為這個頁面不只可以編輯自己的資料,也可以讓管理員改任何人的資料。如果是這樣,GetCurrentUserProfileAsync
這個函數就應該改成 GetUserProfileAsync
,可以加一個參數:userId
。如果 userId
是 null
的話,就需要獲取當前的用戶的 Profile
,如果有 userId
, 那就去獲取對應用戶的 Profile
。
MSDN 上有一篇關於 Repository 模式的說明。寫的比較細。園子里的這篇也寫的不錯。
3. Interface
C# 里通常都是面向 interface 來寫程式的。一開始我不是很適應這種模式,因為同一個類,還需要再寫一個 interface,等於要把所有的函數簽名再抄一遍。如果要修改,也是需要兩處都改。保如果把測試考慮進去的話,就不一樣了。如果是對一個類產生依賴,那麼註入的時候,就需要對這個類進行 mock。如果這個類還有依賴,那整個就悲劇了。如果是使用 interface,這個問題就不存在了。
其實 interface 和 C 語言的 header file 很像。都是定義了函數的簽名,然後在另一個地方來實現這個函數的功能。到了 C# 里,為什麼這種分享確成了一種負擔呢?只是這裡有一個問題:interface 是這種侵入式的語言特性。也就是說:一個類必需要繼承了對應的 interface 才可以做為這個 interface 的實例來使用。如果想對一個沒有修改方法(比如一個 NuGet 包)想添加 interface 的話,就需要一個中間的 Adaptor 來轉換。
4. MaxLength & StringLength
EF Core 的 Model 可以通過 Attribute 來指定一個欄位的長度。同時,在 ViewModel 里,還可以指定 UI 層的驗證。這時候就有兩個東東出來了:StringLength 和 MaxLength。其中 MaxLength 是用來指定 EF 裡面一個 string 欄位的最多可以寫多少個字元,也就是:varchar(n)
里的 n。而 StringLength 則是用來指定 ViewModel 里的驗證長度的。
下一步
下一步,我要給 ProfileController
增加單元測試,把一些邏輯從 Repository 里弄出來,讓 Repository 里儘量少的包含邏輯代碼。這樣,因為是直通底層,只需要最後做集成測試就可以了,就不需要做單元測試了。