MVC 模式初探

专栏导航

← 上一篇:依赖注入与中间件 - ASP.NET Core 核心概念

← 第一篇:编程世界初探

专栏目录

第23章: MVC 模式初探

  • 专栏导航
    • [一、MVC 模式简介](#一、MVC 模式简介)
      • [1.1 什么是 MVC?](#1.1 什么是 MVC?)
      • [1.2 MVC 工作流程](#1.2 MVC 工作流程)
      • [1.3 为什么使用 MVC?](#1.3 为什么使用 MVC?)
    • [二、创建 MVC 项目](#二、创建 MVC 项目)
      • [2.1 使用 Visual Studio 创建](#2.1 使用 Visual Studio 创建)
      • [2.2 使用命令行创建](#2.2 使用命令行创建)
      • [2.3 项目结构](#2.3 项目结构)
    • 三、Controller(控制器)
      • [3.1 控制器的作用](#3.1 控制器的作用)
      • [3.2 创建控制器](#3.2 创建控制器)
      • [3.3 控制器约定](#3.3 控制器约定)
      • [3.4 常见的返回类型](#3.4 常见的返回类型)
    • 四、View(视图)
      • [4.1 视图的作用](#4.1 视图的作用)
      • [4.2 Razor 语法](#4.2 Razor 语法)
        • [Razor 基本语法](#Razor 基本语法)
      • [4.3 创建视图](#4.3 创建视图)
      • [4.4 强类型视图](#4.4 强类型视图)
      • [4.5 ViewData、ViewBag、TempData](#4.5 ViewData、ViewBag、TempData)
    • 五、Model(模型)
      • [5.1 模型的作用](#5.1 模型的作用)
      • [5.2 数据注解(Data Annotations)](#5.2 数据注解(Data Annotations))
      • [5.3 创建输入模型](#5.3 创建输入模型)
    • 六、路由(Routing)
      • [6.1 约定路由](#6.1 约定路由)
      • [6.2 特性路由](#6.2 特性路由)
    • 七、完整示例:图书管理
      • [7.1 创建模型](#7.1 创建模型)
      • [7.2 创建控制器](#7.2 创建控制器)
      • [7.3 创建视图](#7.3 创建视图)
    • 八、本周小结

前面两篇文章我们学习了 Minimal API,这是一种简洁的 API 开发方式。但对于传统的 Web 页面开发,ASP.NET Core 提供了更强大的 MVC(Model-View-Controller) 模式。这周我们将学习 MVC 的核心概念,了解 Model、View、Controller 如何协同工作。

一、MVC 模式简介

1.1 什么是 MVC?

MVC 是一种软件架构模式,将应用程序分为三个核心部分:

组件 职责 说明
Model(模型) 数据和业务逻辑 表示应用程序的数据结构和规则
View(视图) 用户界面 负责显示数据给用户
Controller(控制器) 控制逻辑 处理用户输入,协调 Model 和 View

1.2 MVC 工作流程

复制代码
用户请求
    ↓
┌──────────────────────────────────────┐
│         Controller(控制器)          │
│  - 接收请求                           │
│  - 调用 Model 处理数据                │
│  - 选择 View 显示结果                │
└──────────────────────────────────────┘
    ↓
┌──────────────────────────────────────┐
│          Model(模型)                │
│  - 处理业务逻辑                       │
│  - 从数据库获取数据                   │
│  - 返回数据给 Controller              │
└──────────────────────────────────────┘
    ↓
┌──────────────────────────────────────┐
│           View(视图)                │
│  - 渲染 HTML 页面                     │
│  - 显示 Model 提供的数据              │
│  - 返回给浏览器                       │
└──────────────────────────────────────┘
    ↓
用户看到页面

1.3 为什么使用 MVC?

职责分离 :Model、View、Controller 各司其职

易于维护 :修改视图不会影响业务逻辑

可重用 :Model 和 Controller 可被多个 View 使用

便于测试:可以单独测试每一层

二、创建 MVC 项目

2.1 使用 Visual Studio 创建

  1. 创建新项目
  2. 搜索 "ASP.NET Core Web 应用程序"
  3. 选择模板:Web 应用程序(Model-View-Controller)
  4. 选择框架:.NET 8.0

2.2 使用命令行创建

bash 复制代码
# 创建 MVC 项目
dotnet new mvc -n MyMvcApp

# 运行项目
cd MyMvcApp
dotnet run

2.3 项目结构

复制代码
MyMvcApp/
├── Controllers/         # 控制器目录
│   └── HomeController.cs
├── Views/              # 视图目录
│   ├── Home/
│   │   ├── Index.cshtml
│   │   └── Privacy.cshtml
│   └── Shared/
│       ├── _Layout.cshtml      # 布局文件
│       └── _ValidationScriptsPartial.cshtml
├── Models/              # 模型目录
│   └── ErrorViewModel.cs
├── wwwroot/            # 静态文件(CSS、JS、图片)
├── Program.cs          # 程序入口
└── appsettings.json    # 配置文件

三、Controller(控制器)

3.1 控制器的作用

控制器是应用程序的"大脑":

  • 接收用户的 HTTP 请求
  • 调用业务逻辑
  • 选择视图返回给用户

3.2 创建控制器

csharp 复制代码
// Controllers/HomeController.cs
using Microsoft.AspNetCore.Mvc;

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

    // GET: /Home/About
    public IActionResult About()
    {
        return View();
    }

    // GET: /Home/Contact
    public IActionResult Contact()
    {
        return View();
    }
}

3.3 控制器约定

  • 控制器类名以 Controller 结尾
  • 继承自 ControllerControllerBase
  • 方法称为 Action(动作)
  • Action 方法返回 IActionResult

3.4 常见的返回类型

csharp 复制代码
public class SampleController : Controller
{
    // 返回视图
    public IActionResult Index()
    {
        return View();
    }

    // 返回 JSON
    public IActionResult GetData()
    {
        var data = new { Name = "张三", Age = 25 };
        return Json(data);
    }

    // 返回内容
    public IActionResult GetText()
    {
        return Content("Hello World");
    }

    // 返回文件
    public IActionResult Download()
    {
        byte[] fileBytes = System.IO.File.ReadAllBytes("file.pdf");
        return File(fileBytes, "application/pdf", "file.pdf");
    }

    // 重定向
    public IActionResult RedirectToHome()
    {
        return RedirectToAction("Index", "Home");
    }

    // 返回 404
    public IActionResult NotFoundExample()
    {
        return NotFound();
    }
}

四、View(视图)

4.1 视图的作用

视图负责渲染 HTML 页面,将数据展示给用户。

4.2 Razor 语法

ASP.NET Core 使用 Razor 视图引擎,它允许在 HTML 中嵌入 C# 代码。

Razor 基本语法
html 复制代码
@* 这是注释 *@

@* 输出变量 *@
<p>你好, @ViewBag.Name</p>

@* 代码块 *@
@{
    var message = "Hello";
    var count = 10;
}

@* 条件语句 *@
@if (count > 5)
{
    <p>数量大于 5</p>
}
else
{
    <p>数量小于等于 5</p>
}

@* 循环 *@
<ul>
@foreach (var item in Model)
{
    <li>@item</li>
}
</ul>

@* 表达式 *@
<p>当前时间: @DateTime.Now.ToString("yyyy-MM-dd")</p>

@* HTML 编码(自动) *@
<p>@Html.Raw("<strong>粗体</strong>")</p>  <!-- 不编码 -->
<p><strong>普通文本</strong></p>

4.3 创建视图

视图文件放在 Views/[ControllerName]/ 目录下,文件名与 Action 方法名相同。

html 复制代码
<!-- Views/Home/Index.cshtml -->
@{
    ViewData["Title"] = "首页";
}

<h1>欢迎来到首页</h1>
<p>这是我的第一个 MVC 页面。</p>

4.4 强类型视图

使用 @model 指令声明视图的模型类型。

csharp 复制代码
// Models/Book.cs
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
    public decimal Price { get; set; }
}
csharp 复制代码
// Controllers/BooksController.cs
public class BooksController : Controller
{
    public IActionResult Index()
    {
        List<Book> books = new List<Book>
        {
            new Book { Id = 1, Title = "C# 入门", Author = "张三", Price = 89.5m },
            new Book { Id = 2, Title = "ASP.NET Core", Author = "李四", Price = 99.0m }
        };

        return View(books);  // 传递模型给视图
    }
}
html 复制代码
<!-- Views/Books/Index.cshtml -->
@model List<Book>

@{
    ViewData["Title"] = "图书列表";
}

<h1>图书列表</h1>

<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>书名</th>
            <th>作者</th>
            <th>价格</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var book in Model)
        {
            <tr>
                <td>@book.Id</td>
                <td>@book.Title</td>
                <td>@book.Author</td>
                <td>@book.Price</td>
            </tr>
        }
    </tbody>
</table>

4.5 ViewData、ViewBag、TempData

特性 类型 说明
ViewData 字典 Controller 到 View 传递数据
ViewBag 动态对象 ViewData 的动态包装器
TempData 字典 在多个请求之间传递数据(如重定向)
csharp 复制代码
// Controller
public IActionResult Index()
{
    ViewData["Title"] = "首页";
    ViewData["Message"] = "Hello World";
    
    ViewBag.Name = "张三";
    ViewBag.Age = 25;
    
    TempData["Notice"] = "操作成功";
    
    return View();
}
html 复制代码
<!-- View -->
@ViewData["Title"]
@ViewData["Message"]
@ViewBag.Name
@ViewBag.Age
@TempData["Notice"]

五、Model(模型)

5.1 模型的作用

模型代表应用程序的数据和业务逻辑:

  • 数据模型:表示数据库表或 API 返回的数据
  • 业务逻辑:验证规则、计算逻辑等

5.2 数据注解(Data Annotations)

使用数据注解为模型添加验证规则。

csharp 复制代码
using System.ComponentModel.DataAnnotations;

public class Book
{
    public int Id { get; set; }

    [Required(ErrorMessage = "书名不能为空")]
    [StringLength(100, ErrorMessage = "书名长度不能超过100个字符")]
    public string Title { get; set; }

    [Required(ErrorMessage = "作者不能为空")]
    public string Author { get; set; }

    [Range(0.01, 9999.99, ErrorMessage = "价格必须在 0.01 到 9999.99 之间")]
    public decimal Price { get; set; }

    [DataType(DataType.Date)]
    public DateTime PublishDate { get; set; }
}

5.3 创建输入模型

用于接收用户提交的表单数据。

csharp 复制代码
// Models/CreateBookViewModel.cs
public class CreateBookViewModel
{
    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [Required]
    public string Author { get; set; }

    [Range(0.01, 9999.99)]
    public decimal Price { get; set; }
}

六、路由(Routing)

6.1 约定路由

Program.cs 中配置:

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

URL 映射示例

URL Controller Action id
/ Home Index -
/Home/Index Home Index -
/Home/About Home About -
/Books/Index Books Index -
/Books/Details/5 Books Details 5

6.2 特性路由

在 Controller 或 Action 上使用特性定义路由。

csharp 复制代码
[Route("api/[controller]")]
[ApiController]
public class BooksController : Controller
{
    [HttpGet]
    [Route("all")]
    public IActionResult GetAll()
    {
        return View();
    }

    [HttpGet("{id:int}")]
    [Route("details/{id}")]
    public IActionResult Details(int id)
    {
        return View();
    }
}

七、完整示例:图书管理

7.1 创建模型

csharp 复制代码
// Models/Book.cs
using System.ComponentModel.DataAnnotations;

public class Book
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [Required]
    public string Author { get; set; }

    [Range(0.01, 9999.99)]
    public decimal Price { get; set; }
}

7.2 创建控制器

csharp 复制代码
// Controllers/BooksController.cs
using Microsoft.AspNetCore.Mvc;
using MyMvcApp.Models;

public class BooksController : Controller
{
    private static List<Book> books = new List<Book>
    {
        new Book { Id = 1, Title = "C# 入门", Author = "张三", Price = 89.5m },
        new Book { Id = 2, Title = "ASP.NET Core", Author = "李四", Price = 99.0m }
    };

    // GET: Books
    public IActionResult Index()
    {
        return View(books);
    }

    // GET: Books/Details/5
    public IActionResult Details(int id)
    {
        var book = books.FirstOrDefault(b => b.Id == id);
        if (book == null)
        {
            return NotFound();
        }
        return View(book);
    }

    // GET: Books/Create
    public IActionResult Create()
    {
        return View();
    }

    // POST: Books/Create
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create(Book book)
    {
        if (ModelState.IsValid)
        {
            book.Id = books.Max(b => b.Id) + 1;
            books.Add(book);
            return RedirectToAction(nameof(Index));
        }
        return View(book);
    }

    // GET: Books/Edit/5
    public IActionResult Edit(int id)
    {
        var book = books.FirstOrDefault(b => b.Id == id);
        if (book == null)
        {
            return NotFound();
        }
        return View(book);
    }

    // POST: Books/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Edit(int id, Book book)
    {
        if (id != book.Id)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            var existing = books.FirstOrDefault(b => b.Id == id);
            if (existing != null)
            {
                existing.Title = book.Title;
                existing.Author = book.Author;
                existing.Price = book.Price;
            }
            return RedirectToAction(nameof(Index));
        }
        return View(book);
    }

    // GET: Books/Delete/5
    public IActionResult Delete(int id)
    {
        var book = books.FirstOrDefault(b => b.Id == id);
        if (book == null)
        {
            return NotFound();
        }
        return View(book);
    }

    // POST: Books/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public IActionResult DeleteConfirmed(int id)
    {
        var book = books.FirstOrDefault(b => b.Id == id);
        if (book != null)
        {
            books.Remove(book);
        }
        return RedirectToAction(nameof(Index));
    }
}

7.3 创建视图

html 复制代码
<!-- Views/Books/Index.cshtml -->
@model List<Book>

@{
    ViewData["Title"] = "图书列表";
}

<h1>图书列表</h1>

<p>
    <a asp-action="Create" class="btn btn-primary">添加新书</a>
</p>

<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>书名</th>
            <th>作者</th>
            <th>价格</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>@item.Id</td>
                <td>@item.Title</td>
                <td>@item.Author</td>
                <td>@item.Price</td>
                <td>
                    <a asp-action="Edit" asp-route-id="@item.Id">编辑</a> |
                    <a asp-action="Details" asp-route-id="@item.Id">详情</a> |
                    <a asp-action="Delete" asp-route-id="@item.Id">删除</a>
                </td>
            </tr>
        }
    </tbody>
</table>

八、本周小结

核心知识点

  1. MVC 模式

    • Model(模型):数据和业务逻辑
    • View(视图):用户界面
    • Controller(控制器):控制逻辑
  2. Razor 语法

    • @ 输出变量
    • @{} 代码块
    • @if、@foreach 等控制结构
  3. 数据传递

    • ViewData、ViewBag、TempData
    • 强类型模型
  4. 路由

    • 约定路由
    • 特性路由

实践成果

✅ 创建了 MVC 项目

✅ 理解了 MVC 各组件的职责

✅ 掌握了 Razor 基本语法

✅ 实现了图书管理 CRUD 页面

相关推荐
无风听海11 小时前
.NET10之AppContext
.net
微八度13 小时前
.Net Web API应用部署成windows服务
windows·.net·web api·winddows服务
无风听海19 小时前
.NET10之Middleware 和 Filter
.net
亓才孓1 天前
[Spring MVC]BindingResult
java·spring·mvc
小钻风33661 天前
Spring MVC拦截器的快速应用
java·spring·mvc
weixin_421994782 天前
互联网与 Web 应用简介
.net·.netcore
用户298698530142 天前
C# Word自动化:轻松插入特殊符号,告别手动烦恼!
后端·c#·.net
步步为营DotNet2 天前
深入剖析.NET中Span:零拷贝内存操作的基石
服务器·php·.net