# [112]天方科技 ASP.net core 教育訓練 1120413(MVC vs MVVM、C#存取修飾詞、C#建構子、Dto Class、Http資料繫結來源)
## MVC vs MVVM
### MVC(Model View Controller)
Model 層:模型(用於封裝業務邏輯相關的數據以及對數據的操縱)
View 層:視圖(渲染圖形化界面,也就是所謂的 UI 界面)
Controller 層:控制器(M 和 V 之間的連接器,主要處理業務邏輯,包括顯示數據,界面跳轉,管理頁面生命週期等)

MVC 的優點
1. 提高程式的可維護性,MVC 將一個功能區分成許多片段,讓程式變得容易維護
2. 在 MVC 中,同一個 Model 可以被不同的 View 重用,提高了程式碼的可重用性
3. MVC 的架構容易識別,可以讓工程師快速掌握項目的結構
4. MVC 將一個功能分成了三部分,因此多個工程師可以同時開發不同部分 的程式碼
MVC 的缺點
1. View 與 Controller 綁住了,不易進行 Unit Test
2. 要花費較多的時間進行前置規劃,不適合小型,中等規模的應用程序
3. 要管理較多檔案
### MVVM( Model View ViewModel)
Model 層:Model 層代表了描述業務邏輯和數據的一系列類的集合。它也定義了數據修改和操作的業務規則。
View 層:View 代表了 UI 組件,像 CSS,JQuery,html 等。他只負責展示從 Presenter 接收到的數據。也就是把模型轉化成 UI。
View Model 層:View Model 負責暴漏方法,命令,其他屬性來操作 VIew 的狀態,組裝 model 作爲 View 動作的結果,並且觸發 view 自己的事件。

MVVM 的優點
1. 用 Data Binding 的方式與 View/ViewController 溝通,使 ViewModel 可以不用知道是哪個 View/ViewController
2. 商業邏輯不用與 UI 綁在一起,讓 ViewController 可以瘦身
3. ViewModel 相對容易執行單元測試 (Unit Test)
MVVM 的缺點
1. 大型的應用程式中,Data Binding 會導致相當大的記憶體消耗
2. 無法簡單重用 View/ViewController,因為與 ViewModel 綁定的關係
3. 並無強硬規定 View/ViewController 跟 ViewModel 的關係
(Ex:單向 or 雙向溝通)
4. 不易除錯,不容易找出 bug 是在 Model 還是 View
參考:
[**MVC vs MVVM!MVVM 是什麼?能吃嗎?(上)**](https://hackmd.io/@leoho0722/rJtBdJFJc/https%3A%2F%2Fhackmd.io%2F%40leoho0722%2Frkaj8tXH9)
[**vue 設計模式之 MVC、MVP 和 MVVM 三種設計模式的異同**](https://www.readfog.com/a/1649707274211528704)
[**MVC V.S. MVVM 學習筆記**](https://front-chef.coderbridge.io/2021/02/27/mvc-mvvm/)
## 建立Dto Class
### 為什麼要使用DTO
1. 將服務層與數據庫層分離
2. 隱藏客戶端不需要接收的特定屬性
3. 省略屬性以減小有效負載大小
4. 操作嵌套對像以使它們對客戶更方便
5. 避免「過度張貼」漏洞
### 建立Dto Class時可以建立一個專屬Dto的資料夾,並將檔案名稱加上Dto,容易分辨、搜尋


參考:
[**Day23 DTO是甚麼**](https://ithelp.ithome.com.tw/articles/10306195)
[**DTO、VO、BO、DAO、POJO 各種 Object**](https://hackmd.io/@OceanChiu/HJBvCZcQ8)
## C#屬性
`get`存取子和`set`存取子
```csharp=
public class DtoStudent
{
//完整Property寫法
private string? _names;
public string? Name //Property
{
get { return _names; }
set { _names = value; } //accessor
}
//早期沒有Property的語言,需要額外寫Get以及Set方法
public string? getName() { return _names; }
public void setName(string? value) { _names = value; }
//當前寫法
//private string? _gender; //編譯時自動宣告
public string? Gender { get; set; }; //省略變數名稱,只留get、set
//Property可以使用修飾詞
public string? Gender { get; internal set; };
//新Property init
public string? Gender { get; init; }; //只能在建構函式中使用,或使用 物件初始化運算式
}
```
參考:
[**自動實作的屬性 (C# 程式設計手冊)**](https://learn.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties)
[**使用屬性 (C# 程式設計手冊)**](https://learn.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/using-properties)
[**init 存取子**](https://learn.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/using-properties#the-init-accessor)
## 存取修飾詞
`public`:類型或成員可由相同元件或其他參考它的元件中的其他程式碼存取。 類型的公用成員的存取層級是由類型本身的存取層級所控制。公開
`private`:類型或成員只能由相同 `class` 或 `struct` 中的程式碼存取。類別內部可見
`protected`:類型或成員只能由相同 `class` 中的程式碼存取,或是 `class` 衍生自該`class`中。 只要有繼承就可以使用,可跨專案
`internal`:類型或成員可由相同元件中的任何程式碼存取,但無法從另一個元件存取。 換句話說, `internal` 可以從屬於相同編譯的程式碼存取類型或成員。此專案可用的任何類別
`protected internal`:類型或成員可由宣告所在的元件中的任何程式碼存取,或從衍生自另一個元件中的程式 `class` 代碼存取。只要有繼承就可以使用,可跨專案
`private protected`:類型或成員可由衍生自 class 其包含元件內宣告的 型別存取。除了類別內部,任何繼承都可以使用

參考:
[**存取修飾詞 (C# 程式設計手冊)**](https://learn.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/access-modifiers)
### 最上層型別修飾詞
在`namespace`下直接宣告`Value Type`和`Reference Type`的5種型別,稱為最上層型別
最上層型別只有`public`、`internal`兩種修飾詞
### 最上層型別預設為`internal`,Class內部預設為`public`


`internal` 小愛心圖示

`public`圖示

### `struct`內部可以使用`public`、`internal`、`private`
`private`鎖圖示

### 在`class`內的方法呼叫`struct`的變數,除了`private`外都可以使用

### `class`內部可以使用所有修飾詞

### 創建`class D`繼承`class C`,因為是繼承的關係,除了`private`外都可以使用

### 不使用繼承只能使用`public`、`internal`、`protected internal`

## 建構子
```csharp=
public class DtoStudent
{
private string? _names;
public DtoStudent() //建構子 construtor 簡稱ctor
{
Gender = "abc";
}
public string? Gender { get; set; };
}
```
### 將`class`實體化時,呼叫的方法就是建構子
```csharp=
class XX
{
void M()
{
DtoStudent dtoStudent = new DtoStudent(); //只有在物件建立時會呼叫建構子
}
}
```
### `init` 物件初始化運算式
```csharp=
class XX
{
void M()
{
DtoStudent dtoStudent = new DtoStudent(){ Gender = "123"} ; //init 物件初始化運算式
//如果Property是init無法直接給值
//public string? Gender { get; init; }
dtoStudent.Gender = "aaa"; //error
//如果Property是set可以直接給值
//public string? Gender { get; set; }
dtoStudent.Gender = "aaa";
}
}
```
### `class`實體化簡寫
```csharp=
//設定
public string? Gender { get; set; }
class XX
{
void M()
{
//原先寫法需要在物件建立後賦值
DtoStudent dtoStudent = new DtoStudent();
dtoStudent.Gender = "aaa";
dtoStudent.Name = "bbb";
//↓
//後來更新成物件初始化時就可以賦值,如果將set改為init就只能在初始化時賦值
DtoStudent dtoStudent = new DtoStudent() {Gender = "aaa", Name = "bbb"};
//可省略()
DtoStudent dtoStudent = new DtoStudent { Gender = "123"} ;
//可省略class名稱
DtoStudent dtoStudent = new () { Gender = "123"} ;
}
}
```
### 建構子多載
```csharp=
//如果沒有宣告預設建構子,在compiler時會自動產生
public DtoStudent()
{
Gender = "abc";
}
public DtoStudent(string? gender)
{
Gender=gender;
}
public DtoStudent(string? gender, string? name)
{
Gender = gender;
Name=name;
}
```

### `[]` 也是使用物件初始化設定
```csharp=
//資料驗證設定
[Range(110,120, ErrorMessage ="範圍110~120之間")]
public int? std_year { get; set; }
//↓
// [] 可以想成
var r = new Range(110, 120) {ErrorMessage ="範圍110~120之間")};
```
## 使用Dto Class
### 使用Dto前,須注意存取權限`public class DtoStudent`,權限設為`public`
```csharp=
[HttpPost("year/{year}")]
public IActionResult GetStudentByYear(int year, DtoStudent dtoStudent)
{
//
string name = dtoStudent.Name!;
string gender = dtoStudent.Gender!;
var linq = from s in _context.s30_student
where s.std_year == year
&& (name != null && name.Length > 0 ? s.std_name.StartsWith(name) : true)
&& (gender != null && gender.Length == 1 ? s.std_sex == gender : true)
group s by new { s.cls_id, s.std_year } into g
where g.Count() > 0
select new { cls_id = g.Key.cls_id, year = g.Key.std_year, count = g.Count(), Students = g.ToList() };
return Ok(linq);
}
```
### 使用Dto物件後,參數被包在body裡面
出現`TypeError: Failed to execute 'fetch' on 'Window': Request with GET/HEAD method cannot have body.`
Swagger不允許`GET/HEAD`包含`body`,可使用Postman測試


### 如果沒有定義預設建構子,但是有定義帶參數的建構子,容易造成執行上的error,所以建議不去特別定義建構子
```csharp=
public class DtoStudent
{
//有宣告帶參數的建構子,在使用時必須給予參數才能執行
public DtoStudent(string? gender)
{
Gender=gender;
}
public DtoStudent(string? gender, string? name)
{
Gender = gender;
Name=name;
}
//↓
class XX
{
void M()
{
DtoStudent dtoStudent = new DtoStudent(); //出現error,因為沒有預設建構子
DtoStudent dtoStudent = new DtoStudent("M") { Name = "bbb"}; //需先給參數才能確定使用哪一個建構子
}
}
}
```
### 只有定義帶參數建構子的情況下,使用Postman測試時出現反序列化失敗問題
`[ApiController]`含有序列化與反序列化功能
`Serializer`:將物件轉成Stream Data
`Deserialize`:Stream Data轉成物件

### 恢復預設建構子,或是不定義任何建構子之後,使用Postman測試

## 資料繫結
**[FromQuery]** - 從查詢字串取得值。
**[FromRoute]** - 從路由資料取得值。
**[FromForm]** - 從張貼的表單欄位取得值。
**[FromBody]** - 從要求本文取得值。
**[FromHeader]** - 從 HTTP 標頭取得值。
`HttpGet` Action,預設來源使用`[FromQuery]`
```csharp=
[HttpGet("year/{year}")]
public IActionResult GetStudentByYear(int year, [FromQuery]DtoStudent dtoStudent)
```
### 使用`[FromQuery]`,只用`body`一樣可以帶出資料

### `Query String`和`body`同時存在也可以

### 預設來源設定為`[FromQuery]`後,修改`Query String`與`body`不一致
可以看出資料繫結時會先掃描`Query String`,如果有得到參數,就停止掃描

### `Query String`與`body`不一致的狀況下,預設來源設定為`[FromBody]`後,改為先掃描`Request Body`

### 使用`form-data`傳值


### 使用`x-www-form-urlencoded`傳值
將`Query String`搬入`body`裡

