Web Forms 跟 MVC 差在哪?_ASP.NET Core MVC 專案架構概述

更新 2026.09.21

舊版文章以 ASP.NET MVC 5(.NET Framework)為例。本文改以 ASP.NET Core MVC 為例,使用目前的 LTS 版本 .NET 10。MVC 的概念(Model、View、Controller 與 Routing)沿用,專案結構與路由設定的寫法有變動,文末附有新舊對照表。

以前的 Web Forms 強調事件驅動、Page Life Cycle 與一些狀態儲存的觀念,而 MVC 與 Web Forms 的差別可用下面的圖呈現:

Web Forms 與 MVC 的差別

Web Forms 的缺點

  • 頁面中區分屬於 form 的元素與非 form 的元素
  • 肥大的 ViewState 讓傳送網頁過程的資料量比較大
  • 控制項一開始是為了節省開發時間而封裝,但也讓開發者對 HTML 的控制不夠直覺
  • 不容易撰寫單元測試,也不容易導入 TDD

MVC 帶來的改善

  1. 關注點分離(SoC, Separation of Concerns):開發人員只需要專注在目前的物件上,不會受到其他物件的干擾或連動影響。
  2. 易於測試及分工:M、V、C 的職責拆開後,網頁設計人員和前端可以直接參與 View 層的設計。實務上常由同一個人負責 View 與 Model、Controller 的整合,設計原則是 View 愈單純愈好,不將業務邏輯寫在這裡。
  3. 對 HTTP 輸出有完全的控制權:寫法的風格有點像 Classic ASP。
  4. 更容易導入 IoC(Inversion of Control)與 DI(Dependency Injection):ASP.NET Core 已內建相依性注入,Controller 的建構函式可以直接注入服務。

從 Web Forms 轉移到 MVC 必須捨棄的特性

  1. ViewState
  2. 頁面事件模型(Event Model)
  3. 封裝的控制項(Server Control)
  4. 頁面追蹤機制(Page Trace)

其餘 ASP.NET 的特性仍以不同形式保留,在 ASP.NET Core 中的對應如下:

舊有特性 ASP.NET Core 的對應做法
Application Life Cycle 主機(WebApplication)與中介軟體(Middleware)管線
Web Caching Output Caching、Response Caching、IMemoryCache
Session Session 中介軟體(AddSession、UseSession)
Authentication Authentication 中介軟體(AddAuthentication、UseAuthentication)

MVC 職責

MVC 職責
  • Model(模型):定義資料型態,負責與資料庫溝通、保存程式狀態並執行相應的業務邏輯。常見的做法是以資料庫中某個 Table 的一筆 Record 對應一個 Entity 類別(例如搭配 Entity Framework Core),每個欄位對應一個屬性。
  • View(檢視):網頁畫面(HTML,以及一些 JavaScript、CSS),使用 Razor 語法(.cshtml)。
  • Controller(控制器):判斷並請求某個 Model、判斷並導向某個 View。Controller 接受 Routing 分析出的路由,也負責把 Model 的資料傳給 View 輸出。

Web Forms 與 MVC 的網址對應

Web Forms 遵循的是「網址路徑」即「檔案路徑」。以首頁與關於兩個頁面為例:

  • http://localhost:12081/ 與 http://localhost:12081/Default 對應網站根目錄下的 /Default.aspx 與 /Default.aspx.cs
  • http://localhost:12081/About 對應 /About.aspx 與 /About.aspx.cs

MVC 則是透過 Routing 找出對應的 Controller 與 Action,網址不再等於檔案路徑。

建立 ASP.NET Core MVC 專案

使用命令列建立專案(也可以在 Visual Studio 選擇 ASP.NET Core Web App (Model-View-Controller) 範本):

dotnet new mvc -o MyMvcApp
cd MyMvcApp
dotnet run

專案的主要結構如下:

MyMvcApp/
├── Controllers/          Controller 類別
├── Models/               Model 類別
├── Views/                Razor 檢視(.cshtml)
│   ├── Home/             HomeController 對應的檢視
│   └── Shared/           共用的版面配置(_Layout.cshtml)
├── wwwroot/              靜態檔案(css、js、圖片)
├── appsettings.json      設定檔
└── Program.cs            應用程式進入點與路由設定

首頁 / 對應的程式位置:

  • Views/Home/Index.cshtml
  • Controllers/HomeController.cs

Routing 的設定

ASP.NET Core 不再使用 Global.asax.cs 與 RouteConfig.cs,服務註冊與路由都集中在 Program.cs。範本產生的內容類似下面這樣(不同版本的範本可能略有差異,例如較新的範本會改用 MapStaticAssets 提供靜態檔案):

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

各行的作用:

  • AddControllersWithViews():註冊 MVC(Controller 與 View)所需的服務。
  • UseStaticFiles():提供 wwwroot 資料夾裡的靜態檔案。
  • UseRouting() 與 UseAuthorization():啟用路由比對與授權。
  • MapControllerRoute(...):定義預設的傳統路由(conventional routing)。

路由樣式

pattern: "{controller=Home}/{action=Index}/{id?}"

這一句定義網址路徑如何對應到 MVC 的參數,和舊版的差別是預設值直接寫在樣式裡:

  • {controller=Home}:Controller 的預設值為 Home
  • {action=Index}:Action 的預設值為 Index
  • {id?}:id 是選擇性參數,相當於舊版的 UrlParameter.Optional

MapControllerRoute 的主要參數:

  1. name:路由名稱
  2. pattern:網址路徑要如何對應到 Controller、Action 與路由值,並可內嵌預設值
  3. defaults:額外的預設值(選用,多數情況直接寫在 pattern 內即可)

大括號包起來的名稱是路由變數。{controller} 與 {action} 是核心變數,若網址中沒有提供,就必須有預設值,否則無法對應到 Action。其餘路由變數可以自訂。

舊版的 routes.IgnoreRoute("{resource}.axd/{*pathInfo}") 在 ASP.NET Core 中不再需要,因為已經沒有 .axd 這類的 HttpHandler。

以 About 為例

網址 /Home/About 的對應方式:

  • {controller} 代表 Home
  • {action} 代表 About

底層機制會先找到名為 Home 的 Controller(HomeController),再呼叫其中名為 About 的 public 方法(Action,也就是網頁的進入點)。Controller 由相依性注入容器建立,所以建構函式可以直接注入需要的服務。

HomeController.cs 的內容如下(這裡改寫為與舊版相同的三個 Action,範本預設為 Index、Privacy、Error):

using Microsoft.AspNetCore.Mvc;

namespace MyMvcApp.Controllers;

public class HomeController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public IActionResult About()
    {
        ViewBag.Message = "Your application description page.";

        return View();
    }

    public IActionResult Contact()
    {
        ViewBag.Message = "Your contact page.";

        return View();
    }
}

若只輸入預設的網址(例如 https://localhost:埠號/),路由會使用預設值,也就是 Home 這個 Controller 的 Index Action。

在 MVC 專案中,一個 Action 對應一個 View。HomeController 有 Index、About、Contact 三個 Action,各自對應到 Views/Home/ 底下的 Index.cshtml、About.cshtml 與 Contact.cshtml。

屬性路由(Attribute Routing)

除了傳統路由,ASP.NET Core 也可以直接在 Controller 或 Action 上標註路由屬性,這種做法在 REST API 中特別常見:

[Route("products")]
public class ProductsController : Controller
{
    [HttpGet("{id:int}")]
    public IActionResult Detail(int id)
    {
        return View();
    }
}

上面的 Action 對應網址 /products/5。一般的網頁(Controller 加 View)以傳統路由為主,API 則以屬性路由為主,兩者也可以混用。

新增 View 的常見誤區

新手通常會觀察到 Views 資料夾裡放的是一些前端網頁,並習以為然地直接在裡面新增網頁。在 Visual Studio 中,於 Views/Home 上按右鍵 -> 加入 -> 檢視,選擇 Razor 檢視,輸入名稱 MyWeb。

新增後的 MyWeb.cshtml 可以先寫成下面這樣:

@{
    ViewData["Title"] = "MyWeb";
}

<h1>MyWeb</h1>

新增後直接瀏覽 /Home/MyWeb 會得到 404 Not Found。原因是 MVC 設計上不允許直接瀏覽 View(只有 wwwroot 裡的檔案會被當成靜態檔案提供),必須由 Controller 的 Action 來決定要回傳哪一個 View。這裡找不到資源,就是因為 Controller 沒有定義導向 MyWeb 這個 View 的 Action。

在 HomeController 補上下面的 Action 之後,就能正常顯示:

public IActionResult MyWeb()
{
    return View();
}

反過來,如果有 Action 但找不到對應的 View 檔案,執行時會出現 InvalidOperationException,訊息會列出搜尋過的路徑。

所以在 MVC 中,一律要先有一個 Controller 的 Action 來主導,才能順利指向 View。

新舊對照:ASP.NET MVC 5 與 ASP.NET Core MVC

項目 ASP.NET MVC 5(.NET Framework) ASP.NET Core MVC
應用程式進入點 Global.asax.cs 的 Application_Start Program.cs
路由設定 RouteConfig.RegisterRoutes,使用 routes.MapRoute app.MapControllerRoute
預設值 defaults: new { ..., id = UrlParameter.Optional } 內嵌在樣式中:{controller=Home}/{action=Index}/{id?}
命名空間 System.Web.Mvc Microsoft.AspNetCore.Mvc
Action 回傳型別 ActionResult IActionResult(ActionResult 仍可使用)
View 副檔名 .cshtml(Razor) .cshtml(Razor)
設定檔 Web.config appsettings.json
相依性注入 需要另外整合第三方容器 內建
全域篩選器 FilterConfig.RegisterGlobalFilters 在 AddControllersWithViews 的選項中註冊
打包壓縮 BundleConfig(System.Web.Optimization) 沒有對應的內建功能,改用前端建置工具
靜態檔案 專案內的資料夾可直接提供 只提供 wwwroot,需啟用 UseStaticFiles
伺服器 IIS 內建 Kestrel,可搭配 IIS、Nginx 等

ASP.NET MVC 5 沒有直接升級到 ASP.NET Core 的路徑。既有專案可以評估重寫,或使用 System.Web adapters 等工具逐步遷移。

參考資料