<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>.NET on 愷的大冒險 Kai's Adventure</title><link>https://kaiadv.pages.dev/categories/.net/</link><description>Recent content in .NET on 愷的大冒險 Kai's Adventure</description><generator>Hugo</generator><language>zh-tw</language><lastBuildDate>Sun, 10 May 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://kaiadv.pages.dev/categories/.net/index.xml" rel="self" type="application/rss+xml"/><item><title>從 WinForms 到 Web 之二：前後端分離架構</title><link>https://kaiadv.pages.dev/posts/20260510/</link><pubDate>Sun, 10 May 2026 00:00:00 +0800</pubDate><guid>https://kaiadv.pages.dev/posts/20260510/</guid><description>&lt;p&gt;如果說 MVC 是「後端包辦一切」，那前後端分離就是「後端只管資料，前端只管畫面」；兩者最根本的差異在於渲染發生的位置：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;── MVC ──────────────────────────────────────────────
瀏覽器 ──請求──▶ 後端（組好 HTML）──▶ 瀏覽器（顯示）
── 前後端分離 ────────────────────────────────────────
瀏覽器 ──請求──▶ 後端（回傳 JSON）──▶ 瀏覽器（自行渲染）
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;後端不再負責產生 HTML，它只提供結構化的資料（通常是 JSON）；畫面要長什麼樣子，完全是前端的責任。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="用-aspnet-core-web-api-來說明"&gt;用 ASP.NET Core Web API 來說明&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;後端&lt;/strong&gt;：只負責資料，不碰畫面&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 1&lt;/span&gt;&lt;span&gt;[ApiController]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 2&lt;/span&gt;&lt;span&gt;[Route(&amp;#34;api/[controller]&lt;span style="color:#a5d6ff"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 3&lt;/span&gt;&lt;span&gt;&lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;class&lt;/span&gt; &lt;span style="color:#f0883e;font-weight:bold"&gt;ProductsController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 4&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 5&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;private&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;readonly&lt;/span&gt; AppDbContext _db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 6&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; ProductsController(AppDbContext db) =&amp;gt; _db = db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 7&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 8&lt;/span&gt;&lt;span&gt; [HttpGet]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt; 9&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; Task&amp;lt;IActionResult&amp;gt; GetAll()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;10&lt;/span&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;11&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;var&lt;/span&gt; products = &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; _db.Products.ToListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;12&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; Ok(products); &lt;span style="color:#8b949e;font-style:italic"&gt;// 純 JSON，沒有 HTML&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;13&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;14&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;15&lt;/span&gt;&lt;span&gt; [HttpPost]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;16&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;public&lt;/span&gt; &lt;span style="color:#ff7b72"&gt;async&lt;/span&gt; Task&amp;lt;IActionResult&amp;gt; Create(ProductDto dto)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;17&lt;/span&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;18&lt;/span&gt;&lt;span&gt; &lt;span style="color:#8b949e;font-style:italic"&gt;// 驗證邏輯、商業規則...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;19&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;var&lt;/span&gt; product = &lt;span style="color:#ff7b72"&gt;new&lt;/span&gt; Product { Name = dto.Name, Price = dto.Price };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;20&lt;/span&gt;&lt;span&gt; _db.Products.Add(product);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;21&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;await&lt;/span&gt; _db.SaveChangesAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;22&lt;/span&gt;&lt;span&gt; &lt;span style="color:#ff7b72"&gt;return&lt;/span&gt; CreatedAtAction(nameof(GetAll), &lt;span style="color:#ff7b72"&gt;new&lt;/span&gt; { id = product.Id }, product);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;23&lt;/span&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#6e7681"&gt;24&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;後端回傳的是這樣的 JSON：&lt;/p&gt;</description></item><item><title>從 WinForms 到 Web 之一：認識 MVC 架構</title><link>https://kaiadv.pages.dev/posts/20260509/</link><pubDate>Sat, 09 May 2026 00:00:00 +0800</pubDate><guid>https://kaiadv.pages.dev/posts/20260509/</guid><description>&lt;p&gt;公司有一套已運行多年的內部管理系統，原本是 Windows Forms 應用程式，現在決定整個搬到 Web 平台；新系統起初以 ASP.NET MVC + Razor Page 為主架構，同時也有部分功能採用 Web API 的形式，形成了一種混搭的局面；這讓我開始思考這兩種做法的本質差異是什麼？在什麼情況下應該選哪一種？在討論這個問題之前，得先把 MVC 架構弄清楚。&lt;/p&gt;
&lt;hr&gt;
&lt;h1 id="mvc-是什麼"&gt;MVC 是什麼？&lt;/h1&gt;
&lt;p&gt;MVC 是一種將應用程式切分成三個層次的架構模式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model&lt;/strong&gt;：應用程式的核心，負責管理資料、業務規則與邏輯，獨立於使用者介面&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View&lt;/strong&gt;：資料的視覺呈現，在 ASP.NET 中就是 Razor Page（&lt;code&gt;.cshtml&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Controller&lt;/strong&gt;：中間協調者，接收使用者輸入、呼叫 Model 處理、再選擇適合的 View 回應&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這三層緊密協作，最關鍵的特點是：整個渲染過程發生在伺服器端，瀏覽器只負責顯示已經組好的 HTML。&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;瀏覽器 ──請求──▶ Controller ──呼叫──▶ Model（業務邏輯 + 資料存取）
│ │
│ ▼
│ DB / Repository
│ │
│◀────── 資料 ───────────┘
│
▼ 傳遞資料
View（Razor Page）
│
瀏覽器 ◀──完整 HTML──┘
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="用-aspnet-core-mvc-來說明"&gt;用 ASP.NET Core MVC 來說明&lt;/h2&gt;
&lt;p&gt;一個典型的 MVC 流程如下：&lt;/p&gt;</description></item></channel></rss>