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

  1. Domain model 的結構相應於來自資料庫的 data
  2. View model 的結構相應於畫面呈現
  3. Input model 則是由使用者端或外部系統端輸入的 model <– 這不是包含在 view model 內?

Input model 會和 MVC 的 model binding 機制協同合作,以提供簡便的資料輸入繫結方式

領域驅動設計的分類

領域驅動設計(Domain-Driven Design, DDD)為基礎,將 model 分為三種:

  1. Entity:具有明確辨識能力的 Entities
  2. Value Object:可與其他 Entity 所共用的資料物件,稱為 value object
  3. 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 的比較

DTOPOCO
只有屬性有屬性及方法
負責裝載資料,無法存狀態可保存當下狀態

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 裡
  • 常用的幾個類別集合都支援泛型,例如:ListDictionaryHashtableStackQueue

型別約束

泛型雖然是動態的,依當下傳入的型別而定,但有時候,我們希望能夠限制傳入的型別,C# 裡使用 where 這個字,限制傳入的型別。

例如:

GenericTypeName<T> where T: constraint1, constraint2

constraint1constrain2 即是我們要約束 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 的漏洞