---
# System prepended metadata

title: '[112]天方科技 ASP.net core 教育訓練 1120413(MVC vs MVVM、C#存取修飾詞、C#建構子、Dto Class、Http資料繫結來源)'

---

# [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 之間的連接器，主要處理業務邏輯，包括顯示數據，界面跳轉，管理頁面生命週期等）

![](https://i.imgur.com/hW29MYf.png)



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 自己的事件。

![](https://i.imgur.com/oibnXZv.png)


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，容易分辨、搜尋

![](https://i.imgur.com/wXRoMwi.png)

![](https://i.imgur.com/8f7tyC9.png)

參考：
[**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 其包含元件內宣告的 型別存取。除了類別內部，任何繼承都可以使用

![](https://i.imgur.com/x2A1kVP.png)

參考：
[**存取修飾詞 (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`
![](https://i.imgur.com/jW7VnuE.png)

![](https://i.imgur.com/4IdAcus.png)
`internal` 小愛心圖示
![](https://i.imgur.com/CiYzylM.png)
`public`圖示
![](https://i.imgur.com/h7CIej3.png)

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

`private`鎖圖示
![](https://i.imgur.com/mX8L7bA.png)

### 在`class`內的方法呼叫`struct`的變數，除了`private`外都可以使用
![](https://i.imgur.com/xZFiOQ7.png)

### `class`內部可以使用所有修飾詞
![](https://i.imgur.com/1ONmPN2.png)

### 創建`class D`繼承`class C`，因為是繼承的關係，除了`private`外都可以使用
![](https://i.imgur.com/wDrTB7y.png)

### 不使用繼承只能使用`public`、`internal`、`protected internal`
![](https://i.imgur.com/vHBEYFX.png)

## 建構子
```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;
        }
```
![](https://i.imgur.com/oevg3YG.png)

### `[]` 也是使用物件初始化設定
```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測試
![](https://i.imgur.com/rkbHioU.png)
![](https://i.imgur.com/7w0aJCq.png)

### 如果沒有定義預設建構子，但是有定義帶參數的建構子，容易造成執行上的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轉成物件

![](https://i.imgur.com/2Q2ucC9.png)


### 恢復預設建構子，或是不定義任何建構子之後，使用Postman測試
![](https://i.imgur.com/O6p7qPk.png)

## 資料繫結
**[FromQuery]** - 從查詢字串取得值。
**[FromRoute]** - 從路由資料取得值。
**[FromForm]** - 從張貼的表單欄位取得值。
**[FromBody]** - 從要求本文取得值。
**[FromHeader]** - 從 HTTP 標頭取得值。

`HttpGet`  Action，預設來源使用`[FromQuery]`
```csharp=
[HttpGet("year/{year}")]
public IActionResult GetStudentByYear(int year, [FromQuery]DtoStudent dtoStudent)
```

### 使用`[FromQuery]`，只用`body`一樣可以帶出資料
![](https://i.imgur.com/reD63hL.png)


### `Query String`和`body`同時存在也可以
![](https://i.imgur.com/xPq0otT.png)

### 預設來源設定為`[FromQuery]`後，修改`Query String`與`body`不一致
可以看出資料繫結時會先掃描`Query String`，如果有得到參數，就停止掃描
![](https://i.imgur.com/LsMK0UY.png)

### `Query String`與`body`不一致的狀況下，預設來源設定為`[FromBody]`後，改為先掃描`Request Body`
![](https://i.imgur.com/MSEHsF6.png)

### 使用`form-data`傳值
![](https://i.imgur.com/kBaIAxo.png)

![](https://i.imgur.com/PbNjEhM.png)

### 使用`x-www-form-urlencoded`傳值
將`Query String`搬入`body`裡
![](https://i.imgur.com/0loHbwb.png)

![](https://i.imgur.com/viKGtOM.png)











