MVC 中,演算法由 Controller 提供,而 data 由 Model 提供
Model 中的 data 來自資料庫、 檔案、Web Service、其他應用程式或系統,或不同程式所演算出來的結果,都算是 Model 的範圍
Model 通常會放在專案的 Models 資料夾內,是基於 ASP.NET MVC 所強調「習慣替代配置」原則,這個原則適合小型應用程式,若一開始就知道要開發大型應用程式 Models 資料夾就不適合放系統層次的 Model 物件,而只能放針對應用程式本身的 Model,甚至不放都可以
Model 的類型
Dino 的分類
Dino Esposito 將 ASP.NET MVC 中的 model 分為三種:Domain Model、View Model、Input Model
- Domain model 的結構相應於來自資料庫的 data
- View model 的結構相應於畫面呈現
- Input model 則是由使用者端或外部系統端輸入的 model <– 這不是包含在 view model 內?
Input model 會和 MVC 的 model binding 機制協同合作,以提供簡便的資料輸入繫結方式
領域驅動設計的分類
以領域驅動設計(Domain-Driven Design, DDD)為基礎,將 model 分為三種:
- Entity:具有明確辨識能力的 Entities
- Value Object:可與其他 Entity 所共用的資料物件,稱為 value object
- Service:供應 entity 或 value object 所需資料的程式或動作,稱為 service
Model 的設計
網路上的一種說法:
Model 要肥,Controller 要輕 Fast model, skinny controller
意思是指,model 要顧全整個應用程式需要的資料,controller 必須輕量化以縮短反應時間
一般而言,應用程式需要什麼資料,model 就怎麼設計,這是小型應用程式常見的作法,也就是說,依據畫面決定 model 的結構
然而對中大型應用程式來說,重點會放在如何解決特殊領域的問題,完成特定領域的工作或滿足特定領域的需求,也就是「商業邏輯」的部分
DTO:Model 的一種設計方式
Model 的其中一種常見的設計方式,叫做 DTO(Data Transfer Object, 資料傳輸物件)
DTO 是只有屬性成員(Property-Only)的 class 物件,只有預設建構式(constructor)及屬性存取子(property getter),沒有方法(method)及其他成員
另一種設計方式:POCO
還有另一種常見的設計方式,叫做 POCO(Plain-Old CLR Object)
跟 DTO 不同的是,POCO 可擁有 method,已針對資料進行驗證,並可保存物件當下的狀態
DTO 與 POCO 的比較
DTO | POCO |
---|---|
只有屬性 | 有屬性及方法 |
負責裝載資料,無法存狀態 | 可保存當下狀態 |
Model 的中介層: repository
- model 利用中介層 repository 與資料來源(例如:database)溝通
- 中介層規定資料來源怎麼處理 model,包括資料存取、資料轉換等
- 可進一步使用 interface 及抽象工廠模式(Abstract Factory Pattern)設計具抽換能力(pluggable)的 repository,以提升 model 與不同資料來源整合的相容性
泛型的概念
跟 TypeScript 一樣,ASP.NET 也有泛型(其實應該反過來講才是,因為 TypeScript 是以 C# 為基礎發展出來的)
- 泛型於 .NET Framework 2.0 中首次加入
- .NET Framework 的泛型實作都在
System.Collections.Generic
的 namespace 裡 - 常用的幾個類別集合都支援泛型,例如:
List
、Dictionary
、Hashtable
、Stack
、Queue
型別約束
泛型雖然是動態的,依當下傳入的型別而定,但有時候,我們希望能夠限制傳入的型別,C# 裡使用 where
這個字,限制傳入的型別。
例如:
GenericTypeName<T> where T: constraint1, constraint2
constraint1
與 constrain2
即是我們要約束 T
的型別
型別約束的種類
型別約束的種類大致分為六種:
約束為 struct
where T: struct
- 型別參數必須是實質型別
- 可以指定 nullable 以外的任何實質型別
static void Swap<T>(ref T input1, ref T input2) where T : struct { }
約束為 class
- 型別參數必須是參考型別,其中包含:任何的 class、interface、delegate 或 array 型別
static void Swap<T>(ref T input1, ref T input2) where T : class { }
約束為 new()
- 型別參數必須擁有宣告為 public 的無參數建構式
- 將 new() 條件約束與其他條件約束一起使用時,一定要將其指定為最後一個
static void Swap<T>(ref T input1, ref T input2) where T : new() { }
約束為 <base class name>
- 型別參數必須本身式指定的 base class,或繼承自該 class
static void Swap<T>(ref T input1, ref T input2) where T : BaseEmployee { }
約束為 <interface name>
- 型別參數本身式指定的 interface,或實作該 interface
- 可以同時指定多個 interface 約束
- 限制的 interface 也可以是泛型
static void Swap<T>(ref T input1, ref T input2) where T : IEmployee { }
約束為 U
(另一個泛型)
- T 必須繼承自 U
static void Swap<T, U>(ref T input1, ref U input2) where T : U { }
參考資料: C# Generic Methods - Introduction, Constraints, Examples
大概先知道有哪些泛型約束的種類,要使用到的時候再來深入研究
Model 的實作
- 通常會以 database 的結構來設計
- 以 DTO 來設計,只有屬性,沒有方法
- 要注意 model 各欄位的型別是否與 database 相容
- SQL 指令一定要用參數化查詢法(Parameterized Statement/Query),避免 SQL injection 的漏洞