# [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`裡  
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.