
這章要學會什麼
- 把網站拆成「網頁層、業務規則層、資料層」三個獨立專案,並說出「業務規則層」為什麼不准參考任何其他層。
- 讓一個請求依序經過「控制器→服務→資料介面→資料實作」,並指出「前臺只看得到已發布的服務」這條規則寫在哪一層。
- 分清楚「跨層傳遞用的資料」跟「畫面專用的資料」有什麼不同,並知道怎麼防止有人在表單裡偷塞不該給的欄位。
- 把「會員中心」跟「管理後臺」各自獨立成一個區塊,網址、程式碼、畫面都各自分開。
- 用一套共用版型組出前臺、會員中心、後臺三種畫面,而且在手機、平板、桌面三種寬度都不會跑版。
先備知識
要先完成第 1 章(網站能建置成功、環境檢查頁打得開)。另外要知道網頁版面用的格線系統怎麼分欄、怎麼在不同螢幕寬度切換樣式;會用瀏覽器的開發人員工具切換模擬螢幕寬度;懂基本的介面注入寫法(上一章的環境檢查已經用過)。
觀念白話講
為什麼要把程式拆成好幾層
第 1 章的程式全部擠在網站專案裡,只有一個頁面的時候還好。但這個平台以後會有四個地方要讀同一份服務資料:前臺目錄、會員中心、管理後臺,以及之後要給 AI 用的入口。假設「還沒公開的服務不能出現在公開目錄」這條規則直接寫死在某個網頁的程式碼裡,之後每加一個新入口就得再抄一次這條規則——只要有一處忘了抄,草稿或已下架的服務就會外流出去,而且編譯器完全不會提醒你。
拆層的目的就是讓每一條規則只寫一次、放在固定的地方:網頁層只負責讀網址參數、選畫面;業務規則放在中間的服務層;資料怎麼存取、放在哪種資料庫,交給最底層的資料層。
一個請求怎麼流過四層
| 層 | 只做什麼 | 不可以做什麼 |
|---|---|---|
| 控制器(網頁層) | 讀網址參數、呼叫服務、把結果包成畫面要的格式 | 寫業務規則、直接查資料庫 |
| 服務(業務規則層) | 業務規則:例如公開目錄一定要加上「狀態=已發布」的條件 | 知道資料到底存在記憶體還是正式資料庫 |
| 資料介面 | 只定義「需要什麼資料」,不管怎麼拿到 | 包含任何實作程式碼 |
| 資料實作(資料層) | 依條件真的去把資料拿出來 | 決定誰能看什麼——那是服務層的責任 |
這樣拆最大的好處在下一章會看到:把資料實作從「記憶體假資料」換成「真正的資料庫」時,前三層一行都不用改,只要改一行註冊設定。
這種分層不是靠「資料夾分整齊」自律做到的,而是靠專案間的參考關係讓寫錯的呼叫直接編譯失敗:業務規則層被設定成「誰都不准參考」,如果有人想在業務規則裡直接接資料庫的類別,程式根本編不過——違規在編譯時就被擋下來,不必等到事後被人抓包。
兩種資料模型:跨層傳遞用的,跟畫面專用的
「跨層傳遞的資料」只放乾淨的資料本身,給服務層、資料層、控制器共用;「畫面專用的資料模型」則是某一個頁面才需要的東西,例如搜尋框當下打了什麼字、下拉選單有哪些選項——這種東西只有控制器跟畫面看得到。
畫面專用模型還有一個重要的安全用途:防止使用者亂塞欄位。網頁表單送出的欄位,框架預設會依名稱自動填進物件屬性;如果表單直接對應到一個「連擁有者是誰、狀態是什麼」都有的完整物件,攻擊者只要在送出的資料裡多塞一個「狀態=已發布」,就可能讓一份草稿繞過審核直接被判定為已發布。解法是:畫面專用的表單模型根本沒有那些欄位,多塞的資料自然無處可放、被安靜忽略。
把「會員中心」跟「管理後臺」各自獨立成一個區塊
| 區塊 | 網址開頭 | 之後由誰保護 |
|---|---|---|
| 會員中心 | /Member/... | 下一章:必須先登入 |
| 管理後臺 | /Admin/... | 下一章:必須有管理員角色 |
用一套共用版型組出三種畫面
後臺跟會員中心的頁面被包了兩層版型(外層公版頁首頁尾、內層再加一個側邊選單),前臺頁面只包一層——這樣兩邊的頁首頁尾永遠完全一致,不會各自長出不同的樣子。「服務卡片」「狀態標籤」這類會在很多頁面重複出現的小元件,也各自獨立成可重複使用的元件,不必每個頁面各寫一份。
古典風格的版面規則,寫死成看得到的規範
這門課選定了米白底、墨綠色主色、深棕色次要色、古銅金只用在裝飾線條這套「古典風」配色(古銅金拿來當文字色的話跟米白底對比不夠,所以規定它只能當框線用)。標題用明體字型營造古典感,內文用黑體比較清楚好讀,而且全部用系統內建字型,不必連網路也能正常顯示。
版面寬度規則也寫死了:手機寬度導覽列收合成漢堡選單、後臺表格改成一張一張的卡片;平板寬度導覽列還是收合,但後臺表格改成可以在自己的框框裡左右滑動看,不會把整個頁面撐寬;桌面寬度才展開完整導覽列跟左右並排的側欄。另外規定所有可以按的東西都要有清楚的鍵盤焦點外框,頁面最前面要放一個「跳到主要內容」的隱藏連結,方便只用鍵盤操作的人。
老師示範做了什麼
示範情境:依序打開前臺首頁、服務目錄、會員中心、管理後臺四種頁面,證明它們共用同一套版型;再把瀏覽器視窗從桌面寬度一路縮到手機寬度,證明版面會跟著調整但不會跑版、不會出現不該有的橫向捲動。
老師接著帶大家看程式碼:「公開目錄只顯示已發布的服務」「會員中心只顯示自己的服務」這兩條規則,都只寫在服務層的同一個地方;不管是前臺首頁還是服務目錄頁,呼叫的都是這同一個方法,所以規則永遠只需要維護一份。實測結果:公開目錄共 4 筆(都是已發布),後臺列表共 8 筆(五種狀態都有)。
自己動手的步驟
步驟一:建立業務規則層與資料層兩個新專案
dotnet new classlib -n McpPlatform.Core -o src/McpPlatform.Core
dotnet new classlib -n McpPlatform.Infrastructure -o src/McpPlatform.Infrastructure
dotnet sln add src/McpPlatform.Core src/McpPlatform.Infrastructure
dotnet add src/McpPlatform.Infrastructure reference src/McpPlatform.Core
dotnet add src/McpPlatform.Web reference src/McpPlatform.Core src/McpPlatform.Infrastructure
特別注意:不要對業务規則層(Core)執行任何加參考的指令——它必須維持「誰都不參考」的狀態,這正是分層能生效的關鍵。
步驟二:資料層與服務註冊
在業務規則層定義服務狀態(草稿、已送審、已發布、已退回、已下架五種)、跨層傳遞的資料格式、以及資料介面;在資料層先用記憶體裡的假資料做出介面的實作(下一章才換成真正的資料庫)。接著在啟動設定裡把介面跟實作接起來:
builder.Services.AddScoped<IApiCatalogService, ApiCatalogService>(); // 業務層:每個請求一個新實例
builder.Services.AddSingleton<IApiCatalogRepository, InMemoryApiCatalogRepository>(); // 資料層:目前整個網站共用同一份假資料
app.MapControllerRoute(name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");
建置一次確認沒有錯誤——這時候還沒有畫面,先不要急著執行。
步驟三:控制器、畫面專用模型與區塊
前臺目錄的控制器是最典型的寫法:只收「關鍵字」「分類」兩個網址參數(沒有狀態參數,所以就算有人在網址硬加「狀態=草稿」也不會生效,因為控制器根本不會去讀這個參數),呼叫服務層拿到已發布清單,包成畫面要的格式後選擇要渲染的畫面。
會員中心表單送出時要注意兩件事:先驗證「防偽造請求的隱藏欄位」是不是本站產生的(避免別的網站偷偷幫你送出表單);再用畫面專用的表單模型接收資料——因為這個模型根本沒有「擁有者」「狀態」這些欄位,使用者就算多塞這些欄位也是白費工夫。
步驟四:共用版型與元件、套上古典樣式
建立一個「本身也套用外層版型」的中間層版型,依照目前是「會員中心」還是「管理後臺」放入不同的側邊選單;再把「狀態圖示+文字」這種重複出現的小片段做成一個獨立元件,統一在一個地方決定五種狀態各自要顯示什麼圖示、什麼顏色。最後把古典風的樣式表換上去,所有顏色都集中寫在檔案最上方,方便統一調整。
後臺清單頁用同一份資料輸出兩種畫面:一份是預設隱藏、寬度夠了才顯示的表格;一份是預設隱藏、寬度不夠才顯示的卡片清單——寬度切換時瀏覽器只是切換「哪個顯示、哪個隱藏」,資料只需要準備一份。
常見錯誤
| 症狀 | 原因 | 怎麼處理 |
|---|---|---|
| 建置時說某個檔案被鎖住、無法覆蓋 | 網站還在執行中,編譯出來的檔案正被佔用 | 先停掉正在執行的網站,再重新建置 |
| 後臺頁面編譯錯誤,找不到某個畫面專用類別 | 後臺區塊少了一份跟前臺共用的匯入設定檔 | 幫後臺區塊也複製一份匯入設定;同一份設定也負責讓自訂元件能被辨識 |
| 後臺網址回 404,改打去掉區塊前綴的網址卻回 500 說找不到畫面 | 控制器忘了標示自己屬於哪個區塊,被當成一般控制器處理 | 在控制器上明確標示所屬區塊 |
| 會員中心表單送出後得到失敗、畫面空白 | 手寫的表單標籤沒有自動帶上防偽造隱藏欄位 | 改用框架提供的表單標籤語法,它會自動補上這個欄位 |
怎麼驗收+反向驗證
- 桌面與手機都能導覽:桌面寬度時導覽列三個連結都直接點得到;手機寬度點漢堡選單能展開,會員中心與後臺的側欄在手機寬度可以左右滑動,每個選單項目都點得到。
- 不會出現整頁的橫向捲動:在手機、平板、桌面三種寬度下,每個頁面都要確認「頁面實際內容寬度」跟「可視窗寬度」相等,本章實測的所有組合都是相等的。
- 表單不會爆版:手機寬度打開新增服務表單,欄位要上下直排、不超出畫面,錯誤訊息要出現在對應欄位下方。
- 後臺表格在窄螢幕有替代方案:平板寬度表格可以自己左右滑動、手機寬度整個換成卡片清單。
- 反向驗證:用工具直接送出一個沒有帶防偽造隱藏欄位的表單請求,必須被拒絕,畫面不能出現「表單驗證通過」這種提示。