--- ###### tags: `web111a` `程式筆記` `ASP.NET` `C#` --- # ASP.NET C# MVC * 老師:許志強 * 時間:2022/04/27 ## 淵源 ### .NET Support Policy * 早期是 Webform,後來改 [MVC](#補充:MVC) 架構, * 所以 .Net Framework (目前 4.8 版)問世了,他可以寫Webform 和 .Net Framework,但是沒有跨平台支援 * 於是有 .Net Core (目前 3.1 版) * 後來又有 .Net 6 * 之後預計還會有 .NET 7, .Net 8 ... ![.NET Support Policy](https://i.imgur.com/VRewGSo.png) :::success 本課以 .Net Framework 為主,時間充足會帶到 .Net Core、.Net 6 ::: ~~補充:MVC~~ 2022/5/24 更新:拉到[下方章節](#MVC) ### 那手機呢? * 以前除了寫 C#,手機端還要再學 Android 以及 iOS 開發, * 於是有了 Xamarin,讓你能用 C# 開發手機 APP * 但微軟又双叒叕宣布之後 MAUI 將會取代 Xamarin ### 開發工具 * Visual Studio * 整合性較高,可以寫 .Net Framework、.Net Core * 比較肥大,畢竟還要能寫舊東西 * 只能用在 Windows 平台 * Visual Studio Core * 特別針對跨平台,MacOS、Unix 也可以用 * 業界趨勢,故新技術支援叫豐富 :::success 結論:小孩才做選擇。本課以 Visual Studio 為主,時間充足會帶到 Visual Studio Core ::: #### 版本選擇 * 年份 * 2019 * 2022支援 .Net 6 :ballot_box_with_check: * 版本 * 社群版(微軟會員免費):ballot_box_with_check: * 專業版 * 企業版 ## C\# ### Visual Studio 基操 ### `try/catch` ```csvpreview= try { // 有可能出錯的程式碼 } catch (Exception e) { // 錯誤訊息在這邊撈 Console.WriteLine("Error occurred from {0}. Message = {1}", path, e.Message); } finally { // 無論如何執行這邊 } ``` ### `TryParse` 比起 `try/catch` 不會讓程式中斷,可以再用迴圈讓使用者重新輸入 ```csharp= double.TryParse(str_height, out db1_height) ``` --- * 時間:2022/5/12 ## 物件導向程式設計 OOP :::info 請反覆看老師影片,有些快捷鍵的地方,反覆看反覆看反覆看 ::: 預計花一兩天說明以下: ![成員](https://i.imgur.com/7dfdcmq.png) ## 類別(class) 類別 :arrow_right: new(實作化) :arrow_right: 物件 要 new 之前需要有翻譯的動作:高階語言(人在寫的):arrow_right:低階語言(機器看的) ![class](https://i.imgur.com/a3hy02p.png) * 屬性(property):把物件的特性做歸納(越來越細分) * 方法(method()):物件可以做的動作 * 函數:有回傳值 * 事件:沒有回傳值 ### 實作看看吧 #### 寫程式從 class 開始 ![寫程式從 class 開始](https://i.imgur.com/IwT4uWw.png) namespace 其實就是分類,可以自己取,系統的(program.cs)就不要改 物件導向class裡面的註解是 `///` :::spoiler 註解很重要,之後實體化時都會看得到 ![註解](https://i.imgur.com/LLnNolh.png) ::: 怎麼插入屬性? :a: 輸入 `prop` + 按兩次`tab` ![prop](https://i.imgur.com/QAyxVqE.png) ![prop 完整版](https://i.imgur.com/lwWwEXM.png) 接著把方法、事件補齊 ![方法](https://i.imgur.com/5j4N2zM.png) #### 實作化 從 Program.cs 去 new ![](https://i.imgur.com/TzzpHXw.png) ![實作化](https://i.imgur.com/ti6aiFi.png) 你也許會發現錯誤,因為不同分類(namespace),Program.cs 是 oopdemo 但是我自己寫的 Student.cs 是 mitour,有以下解法: * 放在同一分類 * 明確表示哪個分類,但程式碼會變得很長 ```csharp= mitour.Student Tony = new mitour.Student(); ``` * :ballot_box_with_check:使用 using `using`是什麼? 程式搜尋的路徑,擺得越上面,搜得越快,例如你的類別放在 mitour,如果 `using mitour;` 放第一位搜尋速度會比較快,就不用搜尋 A 沒找到、搜尋 B 沒找到、搜尋 mitour 找到了 ```csharp= using mitour; using A; using B; ``` visual basic 很好心的會幫你尋求解決方案,把滑鼠移到錯誤上看看 ![一盞光明燈](https://i.imgur.com/RYX8p1z.png) :::info 小撇步:using 沒用到會比較暗,有用到會亮起來 ::: ## 枚舉類型 (Enum) 用來限制**程式設計師**輸入,就像下拉式選單 ![枚舉類型](https://i.imgur.com/uVvuvvR.png) ### 實作看看吧 Enum 本質上還是 class,新增 class ![enum](https://i.imgur.com/lGgBik8.png) 建議不要分類,卡好用 自己手動 class 改 enum ![手動class改enum](https://i.imgur.com/hfyRjaG.png) 不編號預設就從 0 開始編號,所以下列不寫0、1、2是一樣的 ![enum](https://i.imgur.com/cIcMSGD.png) 記得加上註解 然後你會發現 Student.cs 中 Gender 就可以改成自己建立的 enGender ![enGender](https://i.imgur.com/RpZszw4.png) 從 Program.cs 去使用 ```csharp= Tony.Gender = enGender.Man; ``` :::spoiler 快捷鍵 1. `=` 完加上 `空白` ![enGender快捷鍵](https://i.imgur.com/CsItvXt.png) 1. `enGender` 完加上 `.` ![enGender快捷鍵](https://i.imgur.com/6PixAcz.png) ::: ## 建構子 在 new 的瞬間就會呼叫建構子 ![建構子](https://i.imgur.com/z6qfw2p.png) * 建構子:在記憶體產生之後馬上執行,可以做的事例如建立預設屬性 * 解構子:在清出記憶體之前可以做的事,譬如斷開資料庫連結 * 多型:原則上同一 class 只能有一個同名建構子,但是如果參數不同就可以,可以讓new的同時把屬性參數帶入,程式碼變簡潔 ### 使用注意事項 ![建構子注意事項](https://i.imgur.com/h0iUc2g.png) ### 實作看看吧 public 之後直接接 class 名(此例 Student) ![建構子](https://i.imgur.com/EOB5Sbm.png) 多型 ![多型建構子](https://i.imgur.com/5NBrrwU.png) 快速建立建構子? :a: `ctor` + 按兩次 `tab` ![快速建立建構子](https://i.imgur.com/ufYwyzy.png) #### 實作化 從 Program.cs 去 new 然後你會發現即便沒有用性別、生日,也能帶出預設值 ![測試建構子](https://i.imgur.com/SbClk5u.png) 測試多型 ![測試多型](https://i.imgur.com/I10MEIF.png) ```csharp= Student Mary = new Student("002", "Mary", enGender.Woman, DateTime.Now); ``` ## 繼承 寫之前先想好階層 ![繼承](https://i.imgur.com/Jt6cnqP.png) ### 實作看看吧 針對父階層「車」建立 class ![car class](https://i.imgur.com/LM4Rrea.png) 再加入子階層「引擎驅動車」 * 注意分類(namespace)應該跟「車」同一類 * 其中需要枚舉類別的記得建立 * 繼承的寫法:在子類別後方加上 `: <父類別>` ```csharp= public class Engine : Car { } ``` ![引擎驅動車](https://i.imgur.com/HWy6XFW.png) visual studio 檢視類別圖,可能需要額外安裝元件,mac 沒有 GG ![檢視類別圖](https://i.imgur.com/Sm10Ppb.png) ## 抽象(Abstract) 不接受實作化,一般作為父階被繼承用。本例 Car 就是 視為一種防呆機制 ![Abstract](https://i.imgur.com/zq5xDgA.png) ## 虛擬(Virtual)、覆寫(Override) * Virtual 寫在父層 * Override 寫在子層 代表子階可以覆寫父階的事件或函數 ![開放複寫](https://i.imgur.com/E5JA6nf.png) ![覆寫](https://i.imgur.com/FMO2xyt.png) ### 實作看看吧 ![實作覆寫](https://i.imgur.com/bU2swDv.png) ## 存取修飾子(Access modifier) * private:只能用在該類別(class)裡面,繼承也不行 * public:任何類別(class)都能用 * internal:同分類(namespace)才能用 * protected:不管分類,有繼承關係才能共用 * protected internal:合集組合技,同分類可以、有繼承關係也可以 ### 注意事項 `erum` 只能是 `public` ## 修飾詞 (static) [為甚麼要宣告static](https://ithelp.ithome.com.tw/articles/10184803) > 當程式中出現new的時候,會跟記憶體申請記憶體空間,當記憶體擁有對應的大小的記憶時就會將其提供給該程式使用,而該程式也能針對該區域的記憶體進行讀寫。 > > 而static變數就是在載入程式後會主動配給記憶體給程式(僅一次),後續無論實例化多少次,記憶體位置都一樣。 > > 實務上,通常會將公共變數全部設定為靜態變數。這樣可以共享記憶體,相對較節省記憶體。 > > 但事情總是有兩面性的,也因為共享記憶體,所以要特別注意存取權限以及存取時機,例如:通常不會在建構式中設定靜態變數,以免造成其他程式誤取。 --- * 時間:2022/5/24 * 預告:時程安排關係先上後面章節 Web 設計,物件導向剩下的部分之後在提 ## 好奇寶寶提問專區 * 怎麼在程式碼裡連接資料庫並抓到欄位 * Ans: 會寫在 web config 中,連線的語法、抓欄位的語法下午會講 * 資料庫密碼怎麼隱藏?版控時避免誤傳 1. 資料庫端不能使用 SA 因為權限太高,安全考量會為了每一個資料庫建立一至多位使用者 1. 在 web config 裡不以明碼寫資料庫密碼,而是自行建立加密、解密動作 * 但不建議,如果你離職要確保後人看得懂,較容易造成管理不易、維護困難 * html 裡怎麼寫 ASP.NET * 利用 [Razor 語言](#Razor-語言) ## MVC * 目的 * 重複使用已寫好的程式碼,簡化應用程式的開發 * 增強程式的可維護性 * 方便團隊合作 * 缺點:耗時 ![MVC model](https://i.imgur.com/P2DPXIH.jpg) ### Model 常譯為「模型」,負責和資料庫溝通 * 邏輯層 * 業務邏輯相關的資料以及對資料的處理方法 * 存取資料 * 伺服端程式語言 範例: * 會員購物有九折、訂單超過一定的金額免運費 * 檢查登入帳號的類型,並依此開放不同權限 * 判斷使用者彼此之間的友好程度 * 過了期但沒被執行的 to-dos 不能被刪除 ### View 常譯為「視圖」,HTML template * 表現層 * 圖像製作 * 前端程式語言 ### Controller 常譯為「控制器」 * 注重 OOP 觀念 * 收發 request/response 的核心 * 主要負責將視覺的靜態頁面轉換成嵌有程式的動態頁面 * 將資料利用表單發送到 Model * 接收並轉換 Model 所回傳的資料並呈現在 View 之上 範例: * 使用者是否需要先登入 (認證) 才可以看到網頁內容? * 使用者是否只能閱讀資料,但不能修改或刪除? * 使用者新增了資料之後,會重新導向至哪個頁面? ![MVC model](https://i.imgur.com/tpz9Zh2.png) ### ref * [[Day 01] 什麼是MVC?能吃嗎?](https://ithelp.ithome.com.tw/articles/10191216) * [MVC架構是什麼?認識 Model-View-Controller 軟體設計模式](https://tw.alphacamp.co/blog/mvc-model-view-controller) ## 系統開發工作區分 ![full stack](https://i.imgur.com/24C05f5.png) :::spoiler 補充:技能樹 ### 前端技能樹 ![front-end](https://i.imgur.com/QdqWDNm.jpg) ### 後端技能樹 ![back-end](https://i.imgur.com/TsgT1zT.jpg) ::: ## 實作 MVC :::warning Windows(.NET framework) 跟 Mac(.Net core 5.0) 介面可能不太一樣,我是用 Mac,但有些截圖可能會用老師的 (Windows) ::: 建立 MVC 專案 :::info 如果你沒有 .NET framework,可能是沒安裝到(預設未安裝),在 Visual Studio Installer 去安裝就可以了 ::: ![建立 MVC 專案](https://i.imgur.com/nXZ4sar.png) 直接執行看看,你會發現什麼都沒設定但是首頁出來了,你甚至不用設定router、port ![index](https://i.imgur.com/0Wz3t5M.png) ### 來看看他幫我們做了什麼吧 在 `Startup.cs` 裡面可以看到 router 設定,Windows 應該在 `App_start` 裡,會有類似下方語法: ![router](https://i.imgur.com/rtLjDCO.png) `HomeController.cs` ![controller](https://i.imgur.com/J0UcOby.png) `index.cshtml` ![view](https://i.imgur.com/6t2UNMO.png) ### layout 每一頁共用樣式,預設檔名 `_layout.cshtml` ![layout](https://i.imgur.com/e9xzXe7.png) ### 指定樣式 左圖:主版頁面、右圖子:版頁面 ![customize stylesheet](https://i.imgur.com/wF1zHvc.png) ### Razor 語言 * 可以在網頁中撰寫 C# * 副檔名是 `.cshtml` 而不是 `.html`,兩者的關係就像是先前學到的 `.php` 跟 `.html` 喔 * [使用 Razor 語法 (C#) ASP.NET Web 程式設計簡介](https://docs.microsoft.com/zh-tw/aspnet/web-pages/overview/getting-started/introducing-razor-syntax-c) 起手式 ```htmlembedded= ... @{ /* C#程式碼 */ } ... ``` ### 動手做:更改 Bootstrap 版本 解決方案 :arrow_right: 管理 NuGet 套件 :arrow_right: 新增或更新 Bootstrap ![更新 NuGet 套件](https://i.imgur.com/vDvez4y.png) 更新 `_layout.cshtml` ![Layout](https://i.imgur.com/KMMA8no.png) 再回到首頁有可能版面會跑掉,因為版本不同,用法也不同,Bootstrap 5 改動滿多的,所以專案剛開始就建議確定使用的版本 :::info Windows(.NET framework) 上預設 Bootstrap 是 3.4.1,更新到 5.1.3 差很多,會跑版的比較明顯 | Version | 3.4.1 | 5.1.3 | | -------- | -------- | -------- | | index | ![3.4.1](https://i.imgur.com/0vLIiHu.png) | ![5.1.3](https://i.imgur.com/fmoU0Va.png) | ::: 依據需要更新版面設定 * [Bootstrap 5.1.3 文件](https://getbootstrap.com/docs/5.1/getting-started/introduction/) 如果樣式一直沒出來,有可能是吃到快取的 css 檔,你可以暴力 `ctrl` + `F5`,或是理性清除快取,如圖: ![清除快取](https://i.imgur.com/4JsaIjw.png) ![清除快取](https://i.imgur.com/pmCq5TK.png) ### 動手做:加入後台 AdminLTE 模板 :::success 感謝老師提供魔法小卡 ::: 將 AdminLTE 加入專案中,不必要的檔案可以刪掉 ![不必要的檔案可以刪掉](https://i.imgur.com/aWazD11.png) * views :arrow_right: share :arrow_right: 新增 `_LayoutAdmin.cshtml` * 把 AdminLTE 中重複的部分抓進去,navbar、sidebar、footer 等等 * 新增 controller * 注意命名規則 xxxController,後方 Controller 不能改 * 新增 View * 從 Controller 中 Index() 類別右鍵 :arrow_right: 新增介面 * 悲哀地告訴你 .NET Core 可能要自己建 * 把 AdminLTE 中內容(main content)的部分抓進去 * 接著就是一步步處例相容性問題,你也可以都換成 NuGet 套件 * `_LayoutAdmin.cshtml` 更新 * bootstrap CSS * bootstrap JS * FontAwesome * JQuery * JQuery UI * `_LayoutAdmin.cshtml` 檔案路徑,例如: * plugins/xxx :arrow_right: ~/AdminLTE/plugins/xxx * dist/xxx :arrow_right: ~/AdminLTE/dist/xxx :::info Bootstrap 5 已經不需要兼容 JQuery ::: 接下來實際執行看看,應該還會有一些兼容問題,因為 AdminTLE 是用 bootstrap 4 寫的,請自行取代掉 將不必要的 js (demo.js)拿掉 * alert * preload #### 切出靜態、動態(抓資料庫)區域 ![靜態、動態(抓資料庫)區域](https://i.imgur.com/PcOKPBC.png) 靜態 * 例如:應用程式名稱、版本 * Web.config :arrow_right: appSetting 中定義 app 靜態參數 ![WEb.config](https://i.imgur.com/gu2zAVt.png) 寫一隻類別 appService 專門用來抓 Web.config,之後只要呼叫 AppService 的屬性就能抓到 記得引入 `System.Web.Configuration` ```csharp= using System.Web.Configuration ``` 屬性: * AppName:應用程式名稱 * AppVer:應用程式版本 * AppInfor:名稱+版本 ```csharp= /// <summary> /// 應用程式專用類別 /// </summary> public static class AppService{ /// <summary> /// 應用程式名稱 /// </summary> public static string AppName { get { object obj_value = WebConfigurationManager.AppSettings["AppName"]; return (obj_Value == null)? "" : obj_value.ToString(); } } /// <summary> /// 應用程式版本 /// </summary> public static string AppVer { get { object obj_value = WebConfigurationManager.AppSettings["AppVer"]; return (obj_Value == null)? "" : obj_value.ToString(); } } /// <summary> /// 應用程式資訊 /// </summary> public static string AppInfo { get { return $"{AppName} {AppVer}"; } } } ``` 注意: * 建議不要用 namespace,別的專案有需要也可以直接 copy 去用 * 建議用 static 靜態類別,不用實作化(new),比較方便 * 設定唯讀,只能 get 不能 set * 利用 `object` 去抓而不是 `string` 比較好判斷 `null` 狀況 * c# 中字串縮寫的方法 `$"名字:{name}"` 再套到 layout(.cshtml) 的 navbar 上 ```htmlembedded= @AppService.AppInfo ``` 還有 footer company info、copyright 等等資訊可以用 Web.config 設定,減少寫死在網頁(html)中 動態 先建立資料庫 :::spoiler 欄位建議 使用者 | 資料行名稱 | 型別 | 允許 Null | 欄位說明 | | ------------- | ------------- | --------- | -------------------- | | rowid | int | X | | | is_valid | bit | V | 帳戶是否驗證過(T/F) | | mno | nvarchar(50) | V | 員工編號 | | mname | nvarchar(50) | V | 員工姓名 | | pwd_user | nvarchar(50) | V | | | role_no | nvarchar(50) | V | 使用者/管理者/廠商 | | code_gender | nvarchar(50) | V | 性別 | | department_no | nvarchar(50) | V | 部門編號 | | title_no | nvarchar(50) | V | 職稱 | | date_onboard | date | V | 到職日 | | date_leave | date | V | 離職日 | | email_contact | nvarchar(50) | V | 聯絡信箱 | | tel_contact | nvarchar(50) | V | 聯絡電話 | | addr_contact | nvarchar(250) | V | 聯絡地址 | | code_valid | nvarchar(50) | V | 驗證碼 | | remark | nvarchar(250) | V | 備註 | 部門 | 資料行名稱 | 型別 | 允許 Null | | ---------- | ------------ | --------- | | rowid | int | X | | mno | nvarchar(50) | V | | mname | nvarchar(50) | V | | remark | nvarchar(50) | V | 職稱 | 資料行名稱 | 型別 | 允許 Null | | ---------- | ------------ | --------- | | rowid | int | X | | mno | nvarchar(50) | V | | mname | nvarchar(50) | V | | remark | nvarchar(50) | V | 角色 | 資料行名稱 | 型別 | 允許 Null | | ---------- | ------------ | --------- | | rowid | int | X | | mno | nvarchar(50) | V | | mname | nvarchar(50) | V | | remark | nvarchar(50) | V | ::: 實務上不會用sa帳號,權限太高,建議額外建立使用者 * 安全性 :arrow_right: 登入 :arrow_right: 新增 1. 一般:使用者名稱,建議就取名跟資料庫一樣,好記XDD 1. 伺服器角色:public 1. 使用者對應:指定資料庫:mvcdemo、權限:db_owner 1. 實體對應:預設 1. 狀態:預設 用 enity framework 將資料庫匯入到專案中,就可以用操作類別的方法操作資料庫 建議命名: * 資料庫名稱+Model,例如 mvcdemoModel * 但如果你的資料庫只有一個,也可以直接叫 dbModel, * 建立時記得 enities、model 時也改成 dbEnities、dbModel 方便管理 :::info MySQL 記得先下載 MySQL for .NET framework driver ::: 新增類別 UserService,記得引入資料庫的 Model ```csharp= using mvcdemo.Models; ``` 屬性: * UserNo * UserName * UserInfo * Role * IsLogin ```csharp= /// <summary> /// 使用者類別 /// </summary> public static class UserScervice{ } public static string UserNo { get; set; } public static string UserName { get; set; } public static enRole Role { get; set; } public static bool IsLogin { get; set; } public static string UserInfo { get { enumClass enclass = new enClass(); string str_role_name = enclass.getRoleName(Role); return $"{UserNo} {UserName} {str_role_name}"; } } /// <summary> /// 登入系統 /// <param name="userNo">帳號</param> /// <param name="userPwd">密碼</param> /// </summary> public static bool Login(string userNo, string userPwd) { using(dbEntities db = new dbEntities()) { Logout(); // 比對輸入資料與資料庫內資料是否相同 var data = db.Users .Where(m => m.mno == userNo && m.pwd_user == userPwd) .FirstOrDefault(); // 帳號或密碼錯誤(亦即資料庫中沒有這筆結果) if(data == null) return false; // 登入成功,更改、取得屬性 IsLogin = true; UserNo = data.mno; UserName = data.mname; enumClass enclass = new enumClass(); // 沒有設定解構子所以只能用實體化不能直接用 using Role = enclass.GetRole(data.enRole); return true; } } /// <summary> /// 登出系統 /// </summary> public static void Logout() { IsLogin = false; UserNo = ""; UserName = ""; Role = enRole.Guest; } ``` :::info 上述35行 * 有運用到 C# 的 [Lambda](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/lambda-expressions) * 簡單可理解為把 Users 這個資料庫丟給 m,有點像變數,讓變數 m 可以抓 Users 的欄位 上述36行 * `.FirstOrDefault()` 表示有符合的話給我第一筆資料,否則抓 default (就是 `null`) ::: 角色枚舉類型(enRole.cs) ```csharp= /// <summary> /// 角色枚舉類型 /// </summary> publin enum enRole { Guest = 0, User = 1, Mis = 2, Vender = 3 } /// <summary> /// 枚舉類型的類別 /// </summary> public partial class enumClass { /// <summary> /// 取得角色名稱 /// <parem name="role">角色</param> /// </summary> public string GetRoleName(enRole role){ string str_value = "undifine"; if(role == enRole.Guest) str_vlaue = "訪客"; if(role == enRole.User) str_vlaue = "使用者"; if(role == enRole.Mis) str_vlaue = "管理員"; if(role == enRole.Vender) str_vlaue = "廠商"; return str_value; } /// <summary> /// 取得角色 /// <parem name="role">角色</param> /// </summary> public enRole GetRole(string role) { enRole en_value = enRole.Guest; if(role == "Guest") en_vlaue = enRole.Guest; if(role == "User") en_vlaue = enRole.User; if(role == "Mis") en_vlaue = enRole.Mis; if(role == "Vender") en_vlaue = enRole.Vender; return en_value; } } ``` #### 新增使用者的 controller 登入頁通常不會有 header、footer,再自訂一個 layoutLite(.cshtml) > :arrow_down_small: _layoutLite.cshtml ```htmlembedded= <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@AppService.AppInfo</title> <link href="~/Content/bootstrap.min.css" rel="stylesheet" /> <link href="~/Content/Site.css" rel="stylesheet" /> <script src="~/Scripts/modernizr-2.8.3.js"></script> <script src="~/Scripts/jquery-3.6.0.min.js"></script> <script src="~/Scripts/bootstrap.min.js"></script> @RenderSection("styles", required: false) @RenderSection("scripts", required: false) </head> <body> <div class="container body-content"> @RenderBody() </div> </body> </html> ``` 登入事件 ![http](https://i.imgur.com/a33RGna.png) > :arrow_down_small: UserController.cs,再一步一步慢慢改 ```csharp= public class UserController : Controller { [HttpGet] public ActionResult Login { return View(); } [HttpPost] public ActionResult Login(Users model) { return View(); } } ``` 要多少欄位拿多少,在 Model 裡面再建立專門給 view 的 ViewModel (本例叫 vmLogin),建立自己要的: * 結構(欄位) * 標題 * Display,要引入 `System.ComponentModel.DataAnnotations` * 打 Display 讓系統自動載入就好 * ![using](https://i.imgur.com/mbSyTqF.png) * 驗證方式 * Required * 其他 * DataType > :arrow_down_small: Model/ViewModel/vmLogin.cs ```csharp= public class vmLogin { [Display(Name = "帳號")] [Required(ErrorMessage = "必填")] public string UserNo { set; get; } [Display(Name = "密碼")] [Required(ErrorMessage = "必填")] [DataType(DataType.Password)] public string Pwd { set; get; } } ``` 改成以下,特別留意第 11 行,Model 從 User 改成自訂的 vmLogin > :arrow_down_small: UserController.cs ```csharp= public class UserController : Controller { [HttpGet] public ActionResult Login { vmLogin model = new vmLogin(); return View(model); } [HttpPost] public ActionResult Login(vmLogin model) { return View(); } } ``` #### 產生 view ![產生 view](https://i.imgur.com/Z3CQz0b.png) 選擇 MVC5 ![MVC5](https://i.imgur.com/OuIgoyQ.png) 記得用 layoutLite ![layoutLite](https://i.imgur.com/0ZCVFcg.png) > :arrow_down_small: login.cshtml ```htmlembedded= @model vmLogin @{ ViewBag.Title = "Login"; Layout = "~/Views/Shared/_LayoutLite.cshtml"; } <h2>Login</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>vmLogin</h4> <hr/> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.UserNo, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.UserNo, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.UserNo, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(model => model.Password, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Password, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Password, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-primary" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> ``` > :arrow_down_small: UserController.cs ```csharp= public class UserController : Controller { [HttpGet] public ActionResult Login { vmLogin model = new vmLogin(); return View(model); } [HttpPost] // model 是使用者輸入的資料 public ActionResult Login(vmLogin model) { // 依據 vmLogin.cs 中的驗證,本例中只有 require 兩欄必填, // 不符合的話 return View(model),你填什麼(View(model) 的 model) 我就給你什麼,白話文:欄位不會清空 if(!ModelState.IsValid) return View(model); // 去資料庫中驗證資料是否正確,判斷寫在 Model if(!UserService.Login(model.UserNo, model.Pwd)) { // 錯誤訊息,參數("顯示欄位", "錯誤訊息") ModelState.AddModelError("UserNo", "帳號或密碼錯誤"); // 不會清空欄位,原理同上14行 return View(model); } // 登入成功,頁面導向後台首頁 return RedirectToAction("Index", "Admin"); } } ``` 改 layoutAdmin 中使用者姓名 ```htmlembedded= @UserService.UserInfo ``` 可以執行看看囉 老師有提供幾隻常用的類別,該改的欄位要改,重構 資料夾架構 ![資料夾架構](https://i.imgur.com/SdXh2LV.png) GmailService 擴充 AppService 功能 資料庫連線方式 * Entity * SQL Server * Docker 資料庫欄位加減密 base 解構子,讓其他地方都可以直接 using,不用再實體化 運用 [repository pattern](https://ithelp.ithome.com.tw/articles/10157484) 降低耦合度 簡單來說是抽離 controller 對資料庫的依賴性,如果日後資料庫換家,譬如 oracle DB,程式不用重寫 其實本質上就是 CRUD 有幾張資料表,就建立幾個類別 repoDepartments ```csharp= using mvcdemo.Models; ``` ## 實作註冊、登入 流程 註冊 :arrow_right: 驗證 :arrow_right: 註冊頁面 :arrow_right: 登入忘記密碼 加密(加鹽) * 可逆 * 不可逆 ```htmlembedded= @Html.ActionLink("","","","","") ``` ## Deploy 方式 * 本機 * 遠端 * 雲端 ### 安裝 IIS(Internet Information Services) 伺服器 控制台 :arrow_right: 程式和功能 :arrow_right: 開啟或關閉 Windows 功能 安裝口訣: * .NET framework * IIS * 除了 FTP Server 確認: 系統管理工具 :arrow_right: Internet Information Services(IIS) 管理員