实例概述:待办事项列表网站
我们将构建一个具备以下功能的网站:

- 首页 (
Index):显示所有待办事项列表,并可以添加新的待办事项。 - 详情页 (
Details):查看单个待办事项的详细信息。 - 创建页 (
Create):一个表单,用于输入新待办事项的信息。 - 编辑页 (
Edit):一个表单,用于修改现有待办事项。 - 删除页 (
Delete):确认并删除待办事项。
第一步:环境准备
在开始之前,请确保您已安装以下工具:
- .NET 8 SDK:从 Microsoft 官网 下载并安装。
- Visual Studio 2025:推荐使用 Visual Studio 2025 Community 版(免费),在安装时,请确保勾选 “.NET 桌面开发” 或 “ASP.NET 和 Web 开发” 工作负载,它会自动包含所有必需的组件。
- SQL Server Express LocalDB:Visual Studio 安装时会默认包含一个轻量级的 SQL Server 版本,名为 LocalDB,我们无需额外安装。
第二步:创建项目
- 打开 Visual Studio 2025。
- 点击“创建新项目”。
- 在搜索框中输入
ASP.NET Core Web App (Model-View-Controller),然后选择它,这个模板为我们搭建好了 MVC 项目的基本结构。 - 点击“下一步”。
- 配置新项目:
- 项目名称:
TodoListApp(或您喜欢的任何名称)。 - 位置:选择一个您希望保存项目的文件夹。
- 解决方案名称:
TodoListApp。 - 取消勾选“将解决方案和项目放在同一目录中”(如果需要)。
- 项目名称:
- 其他信息:
- 框架:确保选择
.NET 8.0。 - 认证类型:选择
无(No Authentication),因为我们不涉及用户登录。 - 配置 HTTPS:保持勾选。
- 不使用顶级语句:保持勾选(对于初学者,传统的
Program.cs结构更容易理解)。
- 框架:确保选择
- 点击“创建”。
Visual Studio 会为您生成一个完整的 MVC 项目模板,并自动进行还原 NuGet 包和构建。
第三步:创建数据模型
模型 是表示应用程序中数据的部分,在我们的例子中,一个待办事项就是一个模型。
- 在 解决方案资源管理器 中,右键单击
Models文件夹。 - 选择
添加->类。 - 将类命名为
TodoItem.cs。 - 粘贴以下代码:
// Models/TodoItem.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace TodoListApp.Models
{
public class TodoItem
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string? Title { get; set; }
[StringLength(500)]
public string? Description { get; set; }
[DataType(DataType.Date)]
public DateTime DueDate { get; set; }
public bool IsCompleted { get; set; }
}
}
代码解释:

Id:主键,EF Core 会自动将其识别为数据库表的主键。[Required]:数据注解,表示此字段在数据库中不能为空。[StringLength(100)]:限制字符串的最大长度。[DataType(DataType.Date)]:指定此字段只存储日期部分。
第四步:设置数据库上下文
数据库上下文 是 EF Core 的核心,它协调模型和数据库之间的交互。
- 在
Models文件夹中,再创建一个类,命名为TodoDbContext.cs。 - 粘贴以下代码:
// Models/TodoDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace TodoListApp.Models
{
public class TodoDbContext : DbContext
{
public TodoDbContext(DbContextOptions<TodoDbContext> options) : base(options)
{
}
// DbSet 代表数据库中的一张表
public DbSet<TodoItem> TodoItems { get; set; }
}
}
第五步:配置依赖注入和连接字符串
现在我们需要告诉我们的应用程序如何使用 TodoDbContext。
- 打开
appsettings.json文件,这是存储应用程序配置的地方。 - 在
ConnectionStrings节点下,添加一个连接字符串,如果该节点不存在,请手动创建它。
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=TodoListAppDb;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
连接字符串解释:
Server=(localdb)\\mssqllocaldb:指向您电脑上的 LocalDB 实例。Database=TodoListAppDb:这是我们将要创建的数据库名称,如果它不存在,EF Core 会自动为我们创建。Trusted_Connection=True:使用 Windows 身份验证登录数据库。
- 配置依赖注入:打开
Program.cs文件,修改它以注册DbContext。
// Program.cs
using Microsoft.EntityFrameworkCore;
using TodoListApp.Models;
var builder = WebApplication.CreateBuilder(args);
// 1. 添加服务到容器。
builder.Services.AddControllersWithViews();
// 2. 配置 DbContext
builder.Services.AddDbContext<TodoDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
// ... 其余代码保持不变 ...
代码解释:

builder.Services.AddDbContext<TodoDbContext>(...):这行代码将TodoDbContext注册为服务,并告诉它使用 SQL Server Provider,连接字符串从appsettings.json中获取,这被称为“依赖注入”。
第六步:创建控制器
控制器 处理传入的 HTTP 请求,并返回响应。
- 在 解决方案资源管理器 中,右键单击
Controllers文件夹。 - 选择
添加->控制器。 - 选择
MVC 控制器 - 空,然后点击“添加”。 - 将控制器命名为
TodoItemsController.cs。 - 粘贴以下代码:
// Controllers/TodoItemsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TodoListApp.Models;
namespace TodoListApp.Controllers
{
public class TodoItemsController : Controller
{
private readonly TodoDbContext _context;
// 通过构造函数注入 DbContext
public TodoItemsController(TodoDbContext context)
{
_context = context;
}
// GET: TodoItems
public async Task<IActionResult> Index()
{
return View(await _context.TodoItems.ToListAsync());
}
// GET: TodoItems/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var todoItem = await _context.TodoItems
.FirstOrDefaultAsync(m => m.Id == id);
if (todoItem == null)
{
return NotFound();
}
return View(todoItem);
}
// GET: TodoItems/Create
public IActionResult Create()
{
return View();
}
// POST: TodoItems/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Title,Description,DueDate,IsCompleted")] TodoItem todoItem)
{
if (ModelState.IsValid)
{
_context.Add(todoItem);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(todoItem);
}
// GET: TodoItems/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem == null)
{
return NotFound();
}
return View(todoItem);
}
// POST: TodoItems/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Id,Title,Description,DueDate,IsCompleted")] TodoItem todoItem)
{
if (id != todoItem.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(todoItem);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!TodoItemExists(todoItem.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(todoItem);
}
// GET: TodoItems/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var todoItem = await _context.TodoItems
.FirstOrDefaultAsync(m => m.Id == id);
if (todoItem == null)
{
return NotFound();
}
return View(todoItem);
}
// POST: TodoItems/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var todoItem = await _context.TodoItems.FindAsync(id);
if (todoItem != null)
{
_context.TodoItems.Remove(todoItem);
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool TodoItemExists(int id)
{
return _context.TodoItems.Any(e => e.Id == id);
}
}
}
第七步:创建视图
视图 是用户看到的 HTML 页面,我们使用 Razor 语法在 HTML 中嵌入 C# 代码。
-
创建 Index 视图:
- 在
TodoItemsController.cs中,右键单击Index方法,选择添加视图。 - 在弹出的窗口中:
- 视图名称:
Index - 模板:
列表 - 模型类:选择
TodoItem (TodoListApp.Models) - 数据上下文类:选择
TodoDbContext (TodoListApp.Models) - 勾选
使用布局页。
- 视图名称:
- 点击“添加”,Visual Studio 会自动生成
Views/TodoItems/Index.cshtml文件。
- 在
-
创建其他视图:
- 对
Details,Create,Edit,Delete方法重复上述步骤,分别选择详细信息、创建、编辑、删除模板。
- 对
生成的视图文件内容如下(以 Index.cshtml 为例,其他类似):
@* Views/TodoItems/Index.cshtml *@
@model IEnumerable<TodoListApp.Models.TodoItem>
@{
ViewData["Title"] = "Index";
}
<h1>待办事项列表</h1>
<p>
<a asp-action="Create">创建新的待办事项</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Title)
</th>
<th>
@Html.DisplayNameFor(model => model.DueDate)
</th>
<th>
@Html.DisplayNameFor(model => model.IsCompleted)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Title)
</td>
<td>
@Html.DisplayFor(modelItem => item.DueDate)
</td>
<td>
@Html.DisplayFor(modelItem => item.IsCompleted)
</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>
Razor 语法解释:
@model IEnumerable<TodoListApp.Models.TodoItem>:告诉此视图接收一个TodoItem列表作为模型。@ViewData["Title"] = "Index":设置页面的标题。@Html.DisplayNameFor(...)和@Html.DisplayFor(...):HTML Helper 方法,用于显示模型数据。<a asp-action="Create">...</a>:Tag Helper,生成一个指向Createaction 的链接。
第八步:应用数据库迁移
我们已经定义了模型和上下文,但数据库表还不存在,EF Core 的“迁移”功能可以帮我们根据模型的变化自动创建和更新数据库。
-
在 Visual Studio 的 “程序包管理器控制台” (Package Manager Console) 中(
工具->NuGet 包管理器->程序包管理器控制台)。 -
确保默认项目是
TodoListApp。 -
执行以下命令:
# 1. 创建一个初始迁移 Add-Migration InitialCreate # 2. 将迁移应用到数据库 Update-Database
命令解释:
Add-Migration InitialCreate:此命令会扫描您的DbContext和模型,发现TodoItems表还不存在,然后生成一个 C# 文件(位于Migrations文件夹),描述如何创建这个表。Update-Database:此命令执行Migrations文件中的指令,在 LocalDB 中创建TodoListAppDb数据库和TodoItems表。
第九步:运行和测试
恭喜!现在一切准备就绪。
- 按下
F5或点击 Visual Studio 工具栏上的“开始调试”按钮。 - 您的默认浏览器将打开,并显示网站的首页。
- 您应该能看到一个“待办事项列表”页面,表格是空的,但有一个“创建新的待办事项”的链接。
- 点击链接,填写表单,然后点击“创建”,您应该会被重定向回列表页,并且能看到您刚刚创建的待办事项。
- 尝试使用“编辑”、“详情”和“删除”链接来管理您的待办事项。
您可以在 Visual Studio 的 “SQL Server 对象资源管理器” (View -> SQL Server Object Explorer) 中连接到 (localdb)\mssqllocaldb,展开数据库,就能看到 TodoItems 表和其中的数据。
总结与进阶
这个实例完整地展示了一个 .NET MVC Web 应用从零到一的开发流程:
- 创建项目:使用模板快速搭建。
- 定义模型:使用 C# 类描述数据结构。
- 配置上下文:设置 EF Core 与数据库的桥梁。
- 配置 DI:将服务(如
DbContext)注入到应用中。 - 创建控制器:编写处理请求的业务逻辑。
- 创建视图:使用 Razor 和 Tag Helper 构建用户界面。
- 应用迁移:自动创建和更新数据库结构。
进阶方向:
- 数据验证:在模型和视图中添加更复杂的验证规则。
- 身份认证:使用 ASP.NET Core Identity 添加用户登录和授权功能。
- API 开发:将
TodoItemsController改造成 Web API,为移动端或前端框架(如 React, Vue)提供数据。 - 前端框架集成:使用 Blazor Server 或 Blazor WebAssembly 在 .NET 中构建交互式 UI。
- 部署:将网站部署到 Azure、IIS 或其他 Web 服务器上。
这个实例为您提供了一个坚实的基础,您可以在此基础上不断扩展和学习更高级的 .NET Web 开发技术。
