【WebAPI】一个基于 ASP.NET Core MVC 8 框架开发的产品管理系统

一、项目概述

1. 项目定位

本项目是一个基于 ASP.NET Core MVC 8 框架开发的产品管理系统,采用“模型-视图-控制器(MVC)”架构,实现了产品数据的完整生命周期管理(增删改查+筛选)。

2. 核心功能

● 产品列表展示:显示所有产品的基本信息(名称、类别、价格、状态等)
● 筛选功能:支持按类别、价格范围、在售状态多条件组合筛选
● 产品操作:添加新产品、编辑已有产品、删除产品、查看产品详情
● 数据持久化:使用 SQLite 数据库存储数据,程序重启后数据不丢失

3. 技术栈

● 框架:ASP.NET Core MVC 8
● 数据库:SQLite(文件型数据库,无需额外安装)
● ORM:Entity Framework Core(EF Core,数据库操作框架)
● 前端:Razor 视图引擎 + Bootstrap(样式框架)
● 工具:EF Core 命令行工具(数据库迁移)

4. 适用场景

● 编程新手学习 MVC 架构、前后端数据交互、数据库操作
● 小型项目的产品管理模块快速搭建
● 教学演示:讲解 HTTP 协议、MVC 分层、数据持久化等核心概念

5. 项目视图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、项目结构详解

1. 整体目录结构

├─ Models/ # 模型层:定义数据结构和规则
│ └─ Product.cs # 产品实体类
├─ Data/ # 数据访问层:数据库交互核心
│ └─ AppDbContext.cs # 数据库上下文(EF Core 核心)
├─ Controllers/ # 控制器层:处理业务逻辑和请求
│ └─ ProductsController.cs # 产品管理控制器
├─ Views/ # 视图层:页面展示模板
│ └─ Products/ # 产品相关视图(与控制器同名)
│ ├─ Index.cshtml # 产品列表+筛选页面
│ ├─ Details.cshtml # 产品详情页面
│ ├─ Create.cshtml # 添加产品表单页面
│ ├─ Edit.cshtml # 编辑产品表单页面
│ └─ Delete.cshtml # 删除产品确认页面
├─ appsettings.json # 项目配置文件(数据库连接等)
├─ Program.cs # 程序入口(服务注册、中间件配置)
└─ ProductDb.db # SQLite 数据库文件(自动生成)

2. 各模块详细说明

1. 模型层(Models):Product.cs

作用:定义产品的数据结构、验证规则,是“数据的身份证”。

using System.ComponentModel.DataAnnotations; // 引入数据验证特性

namespace ProductMvcDemo.Models;

public class Product
{
    /// <summary>
    /// 产品ID(主键,唯一标识)
    /// </summary>
    public int Id { get; set; }

    /// <summary>
    /// 产品名称(必填,最多50个字符)
    /// </summary>
    [Required(ErrorMessage = "产品名称不能为空")] // 非空验证
    [Display(Name = "产品名称")] // 页面显示的标签文本
    [StringLength(50, ErrorMessage = "名称不能超过50个字符")] // 长度限制
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// 产品类别(最多30个字符)
    /// </summary>
    [Display(Name = "产品类别")]
    [StringLength(30)]
    public string Category { get; set; } = string.Empty;

    /// <summary>
    /// 产品价格(必填,必须大于0)
    /// </summary>
    [Required(ErrorMessage = "价格不能为空")]
    [Display(Name = "产品价格")]
    [Range(0.01, double.MaxValue, ErrorMessage = "价格必须大于0")] // 数值范围验证
    public decimal Price { get; set; }

    /// <summary>
    /// 是否在售(默认在售)
    /// </summary>
    [Display(Name = "是否在售")]
    public bool IsActive { get; set; } = true;

    /// <summary>
    /// 创建时间(自动生成,无需用户输入)
    /// </summary>
    [Display(Name = "创建时间")]
    public DateTime CreatedAt { get; set; } = DateTime.Now;
}

关键说明:
● 数据验证特性([Required]、[Range] 等):由 MVC 框架自动验证,不符合规则时页面会显示错误提示
● 主键 Id:EF Core 会自动识别名为 Id 的字段作为数据库表的主键
● 默认值:IsActive = true、CreatedAt = DateTime.Now 避免空值问题

2. 数据访问层(Data):AppDbContext.cs

作用:程序与数据库的“桥梁”,负责将 C# 模型映射到数据库表,执行数据操作。

using Microsoft.EntityFrameworkCore;
using ProductMvcDemo.Models;

namespace ProductMvcDemo.Data;

public class AppDbContext : DbContext
{
    /// <summary>
    /// 构造函数:接收数据库配置(从 Program.cs 注入)
    /// </summary>
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    /// <summary>
    /// 产品数据表:操作数据库的“入口”,对应 SQLite 中的 Products 表
    /// </summary>
    public DbSet<Product> Products => Set<Product>();

    /// <summary>
    /// 初始化测试数据(程序首次运行时执行)
    /// </summary>
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 向 Products 表插入5条测试数据
        modelBuilder.Entity<Product>().HasData(
            new Product { Id = 1, Name = "智能手机", Category = "电子产品", Price = 2999.99m },
            new Product { Id = 2, Name = "笔记本电脑", Category = "电子产品", Price = 5999.99m },
            new Product { Id = 3, Name = "T恤衫", Category = "服装", Price = 99.99m },
            new Product { Id = 4, Name = "运动鞋", Category = "服装", Price = 399.99m, IsActive = false },
            new Product { Id = 5, Name = "咖啡机", Category = "家用电器", Price = 899.99m }
        );
    }
}

关键说明:
● 继承 DbContext:EF Core 的核心基类,提供数据库连接、数据操作的基础功能
● DbSet Products:映射数据库中的 Products 表,后续通过 _context.Products 操作数据(增删改查)
● OnModelCreating:数据库首次创建时执行,用于初始化测试数据,方便直接测试功能

3. 程序入口(Program.cs)

作用:项目的“启动配置中心”,注册服务(数据库、MVC 等)、配置中间件(路由、静态文件等)。

using Microsoft.EntityFrameworkCore;
using ProductMvcDemo.Data;

namespace ProductMvcDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // 1. 创建 Web 应用构建器(负责注册服务)
            var builder = WebApplication.CreateBuilder(args);

            // 2. 注册 MVC 服务(支持控制器和视图)
            builder.Services.AddControllersWithViews();

            // 3. 注册数据库上下文(配置 SQLite 数据库)
            //框架在此配置数据库上下文后,我们就可以在控制器中使用AppDbContext了
            builder.Services.AddDbContext<AppDbContext>(options =>
                options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));

            // 4. 构建应用实例
            var app = builder.Build();

            // 5. 开发环境配置(错误页、HTTPS 重定向等)
            if (!app.Environment.IsDevelopment())
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection(); // HTTP 自动跳转到 HTTPS
            app.UseStaticFiles();  // 支持静态文件(CSS/JS/图片)
            app.UseRouting();     // 路由支持
            app.UseAuthorization();// 授权(暂时用不到)

            // 6. 默认路由:控制器/动作/参数   配置默认路由规则(核心!URL 解析规则)
            app.MapControllerRoute(
                name: "default",
                // 路由模板:{控制器}/{动作}/{可选ID}
                pattern: "{controller=Products}/{action=Index}/{id?}");  // 把默认控制器改为Products
            
            // 7. 启动应用
            app.Run();
        }
    }
}

关键说明:
● 服务注册:AddControllersWithViews() 启用 MVC 功能,AddDbContext 注册数据库上下文(依赖注入)
● 路由规则:{controller=Products}/{action=Index}/{id?} 定义 URL 格式:
○ controller=Products:默认控制器为 ProductsController
○ action=Index:默认方法为 Index(产品列表)
○ id?:可选参数(用于详情、编辑、删除时传递产品ID)
● 中间件顺序:必须按“静态文件→路由→授权→控制器映射”的顺序配置,否则功能失效

4. 配置文件(appsettings.json)

作用:存储项目配置(日志、数据库连接等),无需硬编码修改配置。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*", // 允许所有主机访问
  "ConnectionStrings": {
    // SQLite 数据库连接字符串:数据库文件为 ProductDb.db(项目根目录)
    "DefaultConnection": "Data Source=ProductDb.db"
  }
}

关键说明:
● ConnectionStrings:数据库连接配置,本项目使用 SQLite 时,Data Source=文件名 指定数据库文件路径
● 配置热更新:修改后无需重启程序(开发环境),程序会自动读取最新配置

5. 控制器层(Controllers):ProductsController.cs

作用:处理浏览器请求、执行业务逻辑、操作数据库、返回响应(页面或数据),是“业务逻辑核心”。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ProductMvcDemo.Data;
using ProductMvcDemo.Models;

namespace ProductMvcDemo.Controllers;

// 控制器类名必须以 Controller 结尾(框架自动识别)
public class ProductsController : Controller
{
    // 注入数据库上下文(依赖注入:无需手动创建对象)
    private readonly AppDbContext _context;

    public ProductsController(AppDbContext context)
    {
        _context = context;
    }

    #region 1. 产品列表 + 筛选(GET 请求)
    /// <summary>
    /// 产品列表页面,支持多条件筛选
    /// </summary>
    /// <param name="category">类别筛选(可选)</param>
    /// <param name="minPrice">最低价格(可选)</param>
    /// <param name="maxPrice">最高价格(可选)</param>
    /// <param name="isActive">是否在售(可选)</param>
    /// <returns>产品列表视图</returns>
    [HttpGet] // 明确标记:处理 GET 请求(可省略,默认是 GET)
    public async Task<IActionResult> Index(string? category, decimal? minPrice, decimal? maxPrice, bool? isActive)
    {
        // 1. 从数据库查询所有产品(IQueryable 支持延迟加载,未执行数据库查询)
        IQueryable<Product> products = _context.Products.OrderBy(p => p.Id);

        // 2. 条件筛选:按类别(模糊匹配)
        if (!string.IsNullOrEmpty(category))
        {
            products = products.Where(p => p.Category.Contains(category));
            ViewData["CurrentCategory"] = category; // 保留筛选条件到页面
        }

        // 3. 条件筛选:按最低价格
        if (minPrice.HasValue)
        {
            products = products.Where(p => p.Price >= minPrice.Value);
            ViewData["CurrentMinPrice"] = minPrice;
        }

        // 4. 条件筛选:按最高价格
        if (maxPrice.HasValue)
        {
            products = products.Where(p => p.Price <= maxPrice.Value);
            ViewData["CurrentMaxPrice"] = maxPrice;
        }

        // 5. 条件筛选:按是否在售
        if (isActive.HasValue)
        {
            products = products.Where(p => p.IsActive == isActive.Value);
            ViewData["CurrentIsActive"] = isActive;
        }

        // 6. 执行数据库查询,将数据传递到视图(View)
        return View(await products.ToListAsync());
    }
    #endregion

    #region 2. 产品详情(GET 请求)
    /// <summary>
    /// 查看单个产品的详细信息
    /// </summary>
    /// <param name="id">产品ID(必填)</param>
    /// <returns>产品详情视图</returns>
    [HttpGet]
    public async Task<IActionResult> Details(int? id)
    {
        // 1. 验证 ID 是否为空
        if (id == null)
        {
            return NotFound(); // 返回 404 错误(页面不存在)
        }

        // 2. 查询指定 ID 的产品(FirstOrDefaultAsync:返回第一个匹配项或 null)
        var product = await _context.Products.FirstOrDefaultAsync(p => p.Id == id);
        if (product == null)
        {
            return NotFound(); // 产品不存在,返回 404
        }

        // 3. 将产品数据传递到详情视图
        return View(product);
    }
    #endregion

    #region 3. 添加产品(GET:显示表单;POST:提交数据)
    /// <summary>
    /// 显示添加产品的表单页面(仅展示,不操作数据)
    /// </summary>
    [HttpGet]
    public IActionResult Create()
    {
        return View(); // 返回添加产品表单
    }

    /// <summary>
    /// 提交添加产品的表单数据(操作数据库)
    /// </summary>
    /// <param name="product">表单提交的产品数据(模型绑定自动组装)</param>
    [HttpPost] // 明确标记:处理 POST 请求
    [ValidateAntiForgeryToken] // 防止跨站请求伪造(CSRF)攻击,必须添加
    public async Task<IActionResult> Create([Bind("Id,Name,Category,Price,IsActive")] Product product)
    {
        // 1. 验证表单数据是否符合模型规则(比如名称非空、价格>0)
        if (ModelState.IsValid)
        {
            product.CreatedAt = DateTime.Now; // 手动设置创建时间
            _context.Products.Add(product); // 向数据库添加产品
            await _context.SaveChangesAsync(); // 保存更改(执行 SQL 插入语句)
            return RedirectToAction(nameof(Index)); // 跳回产品列表页面
        }

        // 2. 验证失败:返回表单页面,保留用户输入的错误数据
        return View(product);
    }
    #endregion

    #region 4. 编辑产品(GET:显示表单;POST:提交数据)
    /// <summary>
    /// 显示编辑产品的表单页面(加载现有产品数据)
    /// </summary>
    [HttpGet]
    public async Task<IActionResult> Edit(int? id)
    {
        if (id == null) return NotFound();

        var product = await _context.Products.FindAsync(id); // 根据 ID 查询产品
        if (product == null) return NotFound();

        return View(product); // 将产品数据传递到编辑表单
    }

    /// <summary>
    /// 提交编辑产品的表单数据(更新数据库)
    /// </summary>
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit(int id, [Bind("Id,Name,Category,Price,IsActive,CreatedAt")] Product product)
    {
        // 1. 验证 URL 中的 ID 与表单中的 ID 是否一致
        if (id != product.Id)
        {
            return NotFound();
        }

        // 2. 验证表单数据
        if (ModelState.IsValid)
        {
            try
            {
                _context.Products.Update(product); // 更新产品数据
                await _context.SaveChangesAsync(); // 保存更改(执行 SQL 更新语句)
            }
            catch (DbUpdateConcurrencyException)
            {
                // 处理并发冲突(比如多人同时编辑同一产品)
                if (!ProductExists(product.Id))
                {
                    return NotFound();
                }
                else
                {
                    throw; // 其他异常直接抛出
                }
            }
            return RedirectToAction(nameof(Index)); // 跳回列表页
        }
        return View(product); // 验证失败,返回编辑表单
    }
    #endregion

    #region 5. 删除产品(GET:确认页面;POST:执行删除)
    /// <summary>
    /// 显示删除产品的确认页面
    /// </summary>
    [HttpGet]
    public async Task<IActionResult> Delete(int? id)
    {
        if (id == null) return NotFound();

        var product = await _context.Products.FirstOrDefaultAsync(p => p.Id == id);
        if (product == null) return NotFound();

        return View(product); // 显示确认删除页面
    }

    /// <summary>
    /// 执行删除产品操作(POST 请求,名称为 DeleteConfirmed 避免与 GET 方法冲突)
    /// </summary>
    [HttpPost, ActionName("Delete")] // ActionName:URL 中仍用 Delete,实际执行 DeleteConfirmed
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> DeleteConfirmed(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product != null)
        {
            _context.Products.Remove(product); // 从数据库删除产品
        }

        await _context.SaveChangesAsync(); // 保存更改(执行 SQL 删除语句)
        return RedirectToAction(nameof(Index)); // 跳回列表页
    }
    #endregion

    /// <summary>
    /// 辅助方法:检查产品是否存在(避免重复代码)
    /// </summary>
    private bool ProductExists(int id)
    {
        return _context.Products.Any(p => p.Id == id);
    }
}

关键说明:
● 依赖注入:AppDbContext 通过构造函数注入,无需手动 new 对象,框架自动管理生命周期
● 模型绑定:表单数据或 URL 参数会自动组装成 Product 对象(比如 POST 提交时,表单字段名与 Product 属性名一致即可)
● [ValidateAntiForgeryToken]:防止恶意网站伪造请求提交数据,所有 POST 表单必须添加
● 响应类型:
○ return View(数据):返回指定视图,并传递数据
○ return RedirectToAction(方法名):重定向到其他方法(比如添加成功后跳回列表)
○ return NotFound():返回 404 错误页面

6. 视图层(Views):产品相关页面

作用:页面展示模板,接收控制器传递的数据,生成用户可见的 HTML 页面。
(1)产品列表页:Index.cshtml

@* 声明视图接收的数据类型:产品列表(IEnumerable<Product>)*@
@model IEnumerable<ProductMvcDemo.Models.Product>

@{
    ViewData["Title"] = "产品列表"; // 页面标题
}

<h1>产品管理</h1>
<!-- 添加产品按钮(链接到 Create 方法的 GET 请求) -->
<p>
    <a asp-action="Create" class="btn btn-primary">添加新产品</a>
</p>
<!-- 筛选表单(GET 请求:数据附在 URL 上) -->
<form asp-action="Index" method="get" class="row mb-4">
    <!-- 类别筛选 -->
    <div class="col-md-3">
        <input type="text" name="category" class="form-control" 
               placeholder="输入类别筛选" value="@ViewData["CurrentCategory"]" />
    </div>
    <!-- 最低价格筛选 -->
    <div class="col-md-2">
        <input type="number" step="0.01" name="minPrice" class="form-control" 
               placeholder="最低价格" value="@ViewData["CurrentMinPrice"]" />
    </div>
    <!-- 最高价格筛选 -->
    <div class="col-md-2">
        <input type="number" step="0.01" name="maxPrice" class="form-control" 
               placeholder="最高价格" value="@ViewData["CurrentMaxPrice"]" />
    </div>
    <!-- 状态筛选(下拉框) -->
    <div class="col-md-2">
        <select name="isActive" class="form-control" onchange="this.form.submit()">
            <option value="">全部状态</option>
            <option value="true" @((ViewData["CurrentIsActive"]?.ToString() == "True") ? "selected" : "")>在售</option>
            <option value="false" @((ViewData["CurrentIsActive"]?.ToString() == "False") ? "selected" : "")>下架</option>
        </select>
    </div>
    <!-- 筛选/重置按钮 -->
    <div class="col-md-2">
        <button type="submit" class="btn btn-secondary">筛选</button>
        <a asp-action="Index" class="btn btn-outline-secondary">重置</a>
    </div>
</form>
<!-- 产品表格 -->
<table class="table table-striped table-hover">
    <thead class="table-dark">
        <tr>
            <th>@Html.DisplayNameFor(model => model.Name)</th> <!-- 显示模型属性的 Display 名称 -->
            <th>@Html.DisplayNameFor(model => model.Category)</th>
            <th>@Html.DisplayNameFor(model => model.Price)</th>
            <th>@Html.DisplayNameFor(model => model.IsActive)</th>
            <th>@Html.DisplayNameFor(model => model.CreatedAt)</th>
            <th>操作</th>
        </tr>
    </thead>
    <tbody>
        @* 循环遍历控制器传递的产品列表(Model 即 IEnumerable<Product>)*@
        @foreach (var item in Model)
        {
            <tr>
                <td>@Html.DisplayFor(modelItem => item.Name)</td> <!-- 显示产品名称 -->
                <td>@Html.DisplayFor(modelItem => item.Category)</td> <!-- 显示类别 -->
                <td>¥@item.Price.ToString("0.00")</td> <!-- 价格格式化(保留两位小数) -->
                <td>
                    @* 状态显示(在售=绿色徽章,下架=灰色徽章)*@
                    @if (item.IsActive)
                    {
                        <span class="badge bg-success">在售</span>
                    }
                    else
                    {
                        <span class="badge bg-secondary">下架</span>
                    }
                </td>
                <td>@item.CreatedAt.ToString("yyyy-MM-dd")</td> <!-- 日期格式化 -->
                <td>
                    @* 详情按钮:链接到 Details 方法,传递 id 参数 *@
                    <a asp-action="Details" asp-route-id="@item.Id" class="btn btn-sm btn-info">详情</a>
                    @* 编辑按钮:链接到 Edit 方法 *@
                    <a asp-action="Edit" asp-route-id="@item.Id" class="btn btn-sm btn-warning">编辑</a>
                    @* 删除按钮:链接到 Delete 方法 *@
                    <a asp-action="Delete" asp-route-id="@item.Id" class="btn btn-sm btn-danger">删除</a>
                </td>
            </tr>
        }
    </tbody>
</table>

在这里插入图片描述

关键说明:
● @model IEnumerable:声明视图接收的数据类型,Model 变量即控制器传递的产品列表
● 标签助手(Tag Helper):
○ asp-action=“Create”:自动生成 URL /Products/Create(关联当前控制器 Products)
○ asp-route-id=“@item.Id”:传递路径参数(比如 Details 方法需要的 id)
● ViewData:控制器传递的临时数据(比如筛选条件),用于页面回显
● @Html.DisplayFor:显示模型属性值,自动应用 [Display] 特性的配置

(2)添加产品表单:Create.cshtml

@model ProductMvcDemo.Models.Product

@{
    ViewData["Title"] = "添加产品";
}

<h1>添加新产品</h1>
<hr />
<div class="row">
    <div class="col-md-4">
        @* 表单:提交到 Create 方法(POST 请求)*@
        <form asp-action="Create">
            @* 显示模型验证错误(比如名称为空、价格<=0)*@
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            
            <!-- 产品名称输入框 -->
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label> <!-- 显示 "产品名称" -->
                <input asp-for="Name" class="form-control" /> <!-- 绑定 Name 属性 -->
                <span asp-validation-for="Name" class="text-danger"></span> <!-- 显示该字段的错误提示 -->
            </div>
            
            <!-- 产品类别输入框 -->
            <div class="form-group">
                <label asp-for="Category" class="control-label"></label>
                <input asp-for="Category" class="form-control" />
                <span asp-validation-for="Category" class="text-danger"></span>
            </div>
            
            <!-- 产品价格输入框 -->
            <div class="form-group">
                <label asp-for="Price" class="control-label"></label>
                <input asp-for="Price" class="form-control" step="0.01" /> <!-- step=0.01 支持小数 -->
                <span asp-validation-for="Price" class="text-danger"></span>
            </div>
            
            <!-- 是否在售复选框 -->
            <div class="form-group form-check">
                <label asp-for="IsActive" class="form-check-label"></label>
                <input asp-for="IsActive" class="form-check-input" />
                <span asp-validation-for="IsActive" class="text-danger"></span>
            </div>
            
            <!-- 提交/取消按钮 -->
  @*             这个的创建是一个httppost方法,因为这个页面是从httpget的create打开的,所以这里submit也是给httppost
            这里的取消,走到了controller的Index *@
            <div class="form-group mt-3">
                <input type="submit" value="创建" class="btn btn-primary" />
                <a asp-action="Index" class="btn btn-secondary">取消</a>
            </div>
        </form>
    </div>
</div>
@* 加载验证脚本(用于客户端实时验证,比如输入为空时立即提示)*@
@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

在这里插入图片描述

关键说明:
● asp-for 标签助手:绑定模型属性,自动生成 name、id 属性(比如 asp-for=“Name” 生成 name=“Name”)
● 验证提示:asp-validation-summary 显示全局错误,asp-validation-for 显示单个字段错误
● 客户端验证:_ValidationScriptsPartial 提供实时验证(无需提交表单就知道数据是否合法)
其他视图(Details.cshtml、Edit.cshtml、Delete.cshtml)逻辑类似,核心都是“接收控制器数据→展示/提交数据”。

三、核心原理:前后端数据交互详解

1. 交互核心模型:HTTP 请求-响应模型

浏览器与服务器的交互遵循“请求-响应”模式,类比“写信通信”:
● 浏览器(客户端):写“信”(HTTP 请求),说明“要做什么”(查产品、提交表单)和“附带数据”(筛选条件、表单内容)
● 服务器(Controller):读“信”(解析请求)→ 做事(操作数据库)→ 写“回信”(HTTP 响应),返回“结果”(页面、数据)
● 浏览器:读“回信”→ 展示给用户(渲染 HTML 页面)

2. HTTP 核心方法:GET 与 POST 区别(结合项目)

在这里插入图片描述
项目中 GET 与 POST 的交互流程示例

示例1:查看产品列表(GET 请求)

  1. 浏览器行为:用户输入 URL https://localhost:5001(默认路由→Products/Index)
  2. 请求内容:
    ○ 方法:GET
    ○ URL:https://localhost:5001/Products/Index
    ○ 数据:无(或筛选条件,比如 ?category=电子产品)
  3. 服务器处理(ProductsController.Index 方法):
    ○ 解析请求:是 GET 请求→匹配 [HttpGet] 标记的 Index 方法
    ○ 模型绑定:筛选条件(category=电子产品)自动映射到方法参数 string category
    ○ 业务逻辑:查询数据库→筛选产品→传递数据到 Index 视图
  4. 响应内容:返回 Index.cshtml 渲染的 HTML 页面
  5. 浏览器行为:渲染页面→用户看到产品列表

示例2:添加产品(POST 请求)

  1. 浏览器行为:用户填完 Create 表单→点“创建”按钮
  2. 请求内容:
    ○ 方法:POST
    ○ URL:https://localhost:5001/Products/Create
    ○ 数据:表单内容(Name=无线耳机&Category=电子产品&Price=599.99),放在请求体中
  3. 服务器处理(ProductsController.Create(Product product) 方法):
    ○ 解析请求:是 POST 请求→匹配 [HttpPost] 标记的 Create 方法
    ○ 模型绑定:请求体数据自动组装成 Product 对象(Name 对应 product.Name)
    ○ 数据验证:检查 ModelState.IsValid(是否符合 Product 类的验证规则)
    ○ 业务逻辑:验证通过→添加到数据库→重定向到 Index 方法
  4. 响应内容:返回重定向指令(302 Found),告诉浏览器跳转到 Index 页面
  5. 浏览器行为:自动跳转到产品列表→用户看到新添加的产品

3. 数据传递的3种方式(项目实战)

(1)QueryString(查询字符串):GET 请求传少量数据

● 格式:URL?参数名1=值1&参数名2=值2
● 项目示例:筛选产品时,传递类别和价格条件
○ 浏览器 URL:https://localhost:5001/Products/Index?category=电子产品&minPrice=1000
○ 控制器接收:Index(string? category, decimal? minPrice)(参数名与 URL 参数名一致)
● 适用场景:筛选、分页、简单查询

(2)路径参数:GET 请求传唯一标识(ID)
● 格式:URL/参数值(符合路由规则 {controller}/{action}/{id?})
● 项目示例:查看产品详情时,传递产品 ID
○ 浏览器 URL:https://localhost:5001/Products/Details/1
○ 控制器接收:Details(int? id)(id 对应 URL 中的 1)
● 适用场景:传递唯一标识(详情、编辑、删除操作)

(3)请求体(Request Body):POST 请求传大量/敏感数据
● 格式:数据放在请求体中(不暴露在 URL),格式为 key=value(表单提交)或 JSON
● 项目示例:添加产品时,提交表单数据
○ 请求体数据:Name=无线耳机&Category=电子产品&Price=599.99&IsActive=true
○ 控制器接收:Create(Product product)(模型绑定自动组装 Product 对象)
● 适用场景:表单提交、上传文件、传递敏感数据

4. 控件与 Controller 方法的绑定机制

界面控件(链接、表单)能找到对应的 Controller 方法,核心靠“路由规则+标签助手”,流程如下:
绑定核心前提
● Controller 命名规则:类名以 Controller 结尾(ProductsController→别名 Products)
● 路由规则:{controller}/{action}/{id?}(URL 解析为“控制器/方法/参数”)
场景1:链接控件( 标签)绑定
控件代码(Index.cshtml):

<a asp-action="Details" asp-route-id="@item.Id" class="btn btn-info">详情</a>

绑定流程:

  1. 标签助手解析:asp-action=“Details” 识别当前控制器是 Products(视图在 Views/Products 文件夹),生成 URL:/Products/Details/1(item.Id=1)
  2. 浏览器点击链接:发送 GET 请求到该 URL
  3. 服务器解析 URL:Products→ProductsController,Details→Details 方法,1→id=1
  4. 绑定成功:执行 Details(1) 方法,返回详情页面

场景2:表单控件()绑定
控件代码(Create.cshtml):

<form asp-action="Create">
    <input asp-for="Name" />
    <input type="submit" value="创建" />
</form>

绑定流程:

  1. 标签助手解析:asp-action=“Create” 生成表单 action 属性:/Products/Create,表单默认 method=“post”
  2. 用户提交表单:发送 POST 请求到该 URL,请求体带表单数据
  3. 服务器解析:Products→ProductsController,Create→匹配 [HttpPost] 标记的 Create 方法
  4. 模型绑定:请求体数据自动组装成 Product 对象,传递给方法参数
  5. 绑定成功:执行方法,添加产品到数据库

场景3:下拉框筛选(触发表单提交)
控件代码(Index.cshtml):

<select name="isActive" class="form-control" onchange="this.form.submit()">
    <option value="true">在售</option>
</select>

绑定流程:

  1. 用户选择“在售”:onchange 事件触发表单提交(method=“get”)
  2. 表单数据:isActive=true 拼接到 URL:/Products/Index?isActive=true
  3. 服务器解析:参数 isActive=true 映射到 Index 方法的 bool? isActive 参数
  4. 绑定成功:执行筛选逻辑,返回筛选后的产品列表

四、数据持久化:数据怎么“不丢失”?

1. 什么是数据持久化?

● 临时存储:数据存在内存中(比如程序中的变量),程序重启后数据丢失
● 持久化存储:数据存在硬盘上(比如数据库文件),程序重启后数据保留
本项目使用 SQLite 数据库文件(ProductDb.db)实现持久化,数据存储在硬盘上,关程序后不丢失。

2. 持久化核心:EF Core 与 SQLite 交互

EF Core 是“ORM(对象关系映射)框架”,作用是“翻译官”:
● 开发者写 C# 代码(_context.Products.Add(product))
● EF Core 自动翻译成 SQL 语句(INSERT INTO Products (Name, Category, Price) VALUES (…))
● SQLite 执行 SQL 语句,将数据写入 ProductDb.db 文件

3. 数据库初始化:迁移(Migration)操作(详细步骤)

首次运行项目时,需要通过“迁移”创建数据库和表,步骤如下:
步骤1:安装 EF Core 工具(全局安装)
打开命令提示符(管理员模式),执行:

# 清理 NuGet 缓存(避免安装失败)
dotnet nuget locals all --clear

# 安装 EF Core 命令行工具(与 .NET 8 匹配)
dotnet tool install --global dotnet-ef --version 8.0.0

步骤2:导航到项目根目录

# 示例:项目在桌面的 ProductMvcDemo 文件夹
cd C:\Users\你的用户名\Desktop\ProductMvcDemo\ProductMvcDemo

验证:执行 dir 命令,能看到 ProductMvcDemo.csproj 文件(项目文件)。

步骤3:创建迁移(记录模型到表的映射)

dotnet ef migrations add InitialCreate

● 作用:生成迁移文件,记录 Product 模型对应的数据库表结构
● 成功标志:项目中新增 Migrations 文件夹,包含迁移脚本
● 若失败:检查 Product 类是否有主键(Id 字段)、项目目录是否正确

步骤4:应用迁移(创建数据库和表)

dotnet ef database update

● 作用:执行迁移脚本,创建 ProductDb.db 数据库文件和 Products 表,并插入测试数据
● 成功标志:项目根目录出现 ProductDb.db 文件

步骤5:验证数据库
安装 SQLite 可视化工具(比如 DB Browser for SQLite):

  1. 打开工具→点击“打开数据库”→选择 ProductDb.db
  2. 左侧选择 Products 表→点击“浏览数据”→能看到 5 条测试数据,说明数据库创建成功

4. 数据操作对应关系(C# 代码 → SQL 语句)

在这里插入图片描述

五、项目运行与测试步骤

1. 运行前准备

● 安装 .NET 8 SDK(https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0)
● 完成数据库迁移(生成 ProductDb.db 文件)

2. 运行项目

# 导航到项目根目录
cd 你的项目路径/ProductMvcDemo

# 启动项目
dotnet run

启动成功后,终端会显示监听 URL:

Now listening on: https://localhost:5001
Now listening on: http://localhost:5000

3. 测试功能(浏览器访问)

  1. 访问 https://localhost:5001→进入产品列表页面
  2. 测试筛选功能:输入类别“电子产品”→点击“筛选”→只显示电子产品
  3. 测试添加产品:点击“添加新产品”→填表单→点击“创建”→列表新增产品
  4. 测试编辑产品:点击某产品的“编辑”→修改价格→点击“保存”→产品价格更新
  5. 测试删除产品:点击某产品的“删除”→点击“删除”→产品从列表消失
  6. 验证持久化:关闭程序→重新运行→之前添加/修改/删除的记录仍存在

4. 常见问题排查

在这里插入图片描述

六、核心知识点总结

  1. MVC 架构:Model(数据结构+规则)→ Controller(业务逻辑+请求处理)→ View(页面展示),职责分离,易维护。
  2. HTTP 协议:GET 查数据,POST 改数据,数据传递方式不同(URL/请求体)。
  3. 路由与绑定:URL 按 {controller}/{action}/{id?} 解析,控件通过标签助手(asp-action)生成 URL,实现与 Controller 方法的绑定。
  4. 数据持久化:通过 EF Core 操作 SQLite 数据库,迁移功能实现模型到表的映射,数据存储在文件中不丢失。
  5. 验证机制:模型特性([Required])+ 客户端脚本 + 服务器验证,确保数据合法。
  6. .GET 请求:用于“查数据、显示页面”,数据通过 路径参数(ID 类数据)或 QueryString(筛选条件)传递,数据可见但长度有限。
  7. POST 请求:用于“改数据(增删改)”,数据通过 请求体(表单数据)传递,数据隐藏且无长度限制,需加 [ValidateAntiForgeryToken] 防攻击。
  8. 模型绑定:框架自动将“请求数据”(路径参数、QueryString、请求体)转换为 Controller 方法的“参数/对象”,无需手动解析。
  9. 响应逻辑:
    ○ 成功操作(增删改):重定向到列表页(RedirectToAction),避免重复提交。
    ○ 失败操作(数据验证不通过):返回原表单页面(View(数据)),回显错误数据和提示。
    所有流程的核心都是“前端触发请求→数据按规则传递→Controller 处理→数据库交互→前端展示结果”,掌握这个规律就能理解所有 MVC 项目的交互逻辑!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值