我们将创建一个经典的“待办事项列表”(To-Do List)应用,这是一个非常适合展示 ASP.NET 核心功能的例子。

第一步:准备工作

在开始之前,请确保你已经安装了以下软件:

  1. .NET 6 SDK 或更高版本:这是运行和构建 ASP.NET 应用程序的基础。
  2. Visual Studio 2025:这是开发 ASP.NET 应用最流行的集成开发环境。

第二步:创建 ASP.NET Web 应用项目

  1. 打开 Visual Studio 2025。
  2. 点击“开始” -> “创建新项目”。
  3. 在模板搜索框中输入 ASP.NET Core,然后选择 “ASP.NET Core Web 应用” 模板,确保选择的是 C# 版本。
  4. 点击“下一步”。
  5. 为你的项目命名,TodoWebApp,并选择一个位置来保存项目。
  6. 点击“下一步”。
  7. 在“其他信息”页面:
    • 框架:选择最新的 .NET 版本(如 .NET 8.0)。
    • 配置:选择 “Web 应用”,这会为我们创建一个使用 Razor Pages 的最小化项目,非常适合入门。
    • 其他信息:保持默认即可。
  8. 点击“创建”。

Visual Studio 会为你生成一个基本的项目结构,并包含一些示例页面。


第三步:理解项目结构

让我们快速了解一下 Visual Studio 为我们创建的关键文件和文件夹:

  • Pages/:这个文件夹包含了所有基于 Razor 的页面,每个 .cshtml 文件都是一个网页,而对应的 .cshtml.cs 文件是该页面的 C# 代码后端(称为“代码隐藏模型”)。
    • Index.cshtmlIndex.cshtml.cs:网站的首页。
    • Privacy.cshtmlPrivacy.cshtml.cs:一个隐私页面。
    • Error.cshtmlError.cshtml.cs:用于处理错误。
  • wwwroot/:存放所有静态文件的地方,如 CSS 样式表、JavaScript 文件、图片等。
  • appsettings.json:应用程序的配置文件,用于存储数据库连接字符串、API 密钥等。
  • Program.cs:应用程序的入口点,在这里配置服务(如数据库、中间件)和请求管道。
  • Startup.cs (在较新版本中,代码可能直接在 Program.cs 中):配置服务和应用的中间件。

第四步:创建“待办事项”功能

我们来动手实现核心功能。

定义数据模型

我们需要一个“待办事项”的类来表示数据。

  1. TodoWebApp 项目上右键 -> “添加” -> “新建文件夹”,命名为 Models
  2. Models 文件夹上右键 -> “添加” -> “类”,命名为 TodoItem.cs
  3. 打开 TodoItem.cs,并编写以下代码:
// Models/TodoItem.cs
namespace TodoWebApp.Models
{
    public class TodoItem
    {
        public int Id { get; set; }
        public string? Task { get; set; } // 使用 ? 表示可以为空
        public bool IsDone { get; set; }
    }
}

创建服务来管理数据

为了简单起见,我们不使用数据库,而是使用一个静态列表来存储待办事项,在实际应用中,你会使用数据库。

  1. TodoWebApp 项目上右键 -> “添加” -> “新建文件夹”,命名为 Services
  2. Services 文件夹上右键 -> “添加” -> “类”,命名为 TodoService.cs
  3. 编写代码来管理待办事项列表:
// Services/TodoService.cs
using TodoWebApp.Models;
namespace TodoWebApp.Services
{
    public class TodoService
    {
        private readonly List<TodoItem> _todos = new();
        private int _nextId = 1;
        public List<TodoItem> GetAllTodos()
        {
            return _todos;
        }
        public TodoItem? GetTodoById(int id)
        {
            return _todos.FirstOrDefault(t => t.Id == id);
        }
        public void AddTodo(string task)
        {
            if (!string.IsNullOrWhiteSpace(task))
            {
                _todos.Add(new TodoItem { Id = _nextId++, Task = task, IsDone = false });
            }
        }
        public void UpdateTodo(TodoItem todo)
        {
            var existingTodo = GetTodoById(todo.Id);
            if (existingTodo != null)
            {
                existingTodo.Task = todo.Task;
                existingTodo.IsDone = todo.IsDone;
            }
        }
        public void DeleteTodo(int id)
        {
            var todo = GetTodoById(id);
            if (todo != null)
            {
                _todos.Remove(todo);
            }
        }
    }
}

注册服务

我们需要告诉应用程序 TodoService 是一个可用的服务。

打开 Program.cs 文件,在 builder.Services.AddRazorPages(); 这一行下面添加:

// Program.cs
using TodoWebApp.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
// 注册我们的 TodoService 为单例服务
builder.Services.AddSingleton<TodoService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();

创建网页来显示和操作待办事项

我们将修改首页来展示我们的待办事项列表。

打开 Pages/Index.cshtml 文件,将其内容替换为:

@* Pages/Index.cshtml *@
@page
@model TodoWebApp.Pages.IndexModel
@{
    ViewData["Title"] = "待办事项列表";
}
<div class="text-center">
    <h1 class="display-4">我的待办事项</h1>
</div>
<div class="container mt-4">
    <div class="row">
        <div class="col-md-8 offset-md-2">
            <!-- 添加新待办事项的表单 -->
            <form method="post" class="mb-4">
                <div class="input-group">
                    <input type="text" class="form-control" asp-for="NewTask" placeholder="输入新的待办事项..." />
                    <button type="submit" class="btn btn-primary">添加</button>
                </div>
                <span asp-validation-for="NewTask" class="text-danger"></span>
            </form>
            <!-- 待办事项列表 -->
            @if (!Model.Todos.Any())
            {
                <div class="alert alert-info">
                    暂无待办事项,快来添加一个吧!
                </div>
            }
            else
            {
                <ul class="list-group">
                    @foreach (var todo in Model.Todos)
                    {
                        <li class="list-group-item d-flex justify-content-between align-items-center @(todo.IsDone ? "list-group-item-success" : "")">
                            <div>
                                <input type="checkbox" class="form-check-input me-2" asp-for="@todo.IsDone" 
                                       onchange="submitForm('@Url.Page("Index", "Update", new { id = todo.Id })', { 'IsDone': this.checked })">
                                <span class="@todo.IsDone ? "text-decoration-line-through" : "")">@todo.Task</span>
                            </div>
                            <a href="@Url.Page("Index", "Delete", new { id = todo.Id })" 
                               class="btn btn-sm btn-outline-danger"
                               onclick="return confirm('确定要删除这个待办事项吗?')">删除</a>
                        </li>
                    }
                </ul>
            }
        </div>
    </div>
</div>
@section Scripts {
    <script>
        function submitForm(url, data) {
            fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
                },
                body: JSON.stringify(data)
            })
            .then(response => {
                if (!response.ok) {
                    throw new Error('Network response was not ok');
                }
                window.location.reload(); // 简单刷新页面以显示更改
            })
            .catch(error => console.error('Error:', error));
        }
    </script>
}

打开 Pages/Index.cshtml.cs 文件,替换其内容为:

// Pages/Index.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using TodoWebApp.Models;
using TodoWebApp.Services;
namespace TodoWebApp.Pages
{
    public class IndexModel : PageModel
    {
        private readonly TodoService _todoService;
        // 通过构造函数注入服务
        public IndexModel(TodoService todoService)
        {
            _todoService = todoService;
        }
        public List<TodoItem> Todos { get; set; } = new();
        [BindProperty(SupportsGet = true)]
        public string? NewTask { get; set; }
        public void OnGet()
        {
            // 页面加载时,获取所有待办事项
            Todos = _todoService.GetAllTodos();
        }
        public IActionResult OnPost()
        {
            if (!string.IsNullOrWhiteSpace(NewTask))
            {
                _todoService.AddTodo(NewTask);
            }
            return RedirectToPage(); // 重定向到当前页面,以清空输入框并刷新列表
        }
        public IActionResult OnPostUpdate(int id, bool isDone)
        {
            var todo = _todoService.GetTodoById(id);
            if (todo != null)
            {
                todo.IsDone = isDone;
                _todoService.UpdateTodo(todo);
            }
            return new OkResult(); // 返回成功状态
        }
        public IActionResult OnPostDelete(int id)
        {
            _todoService.DeleteTodo(id);
            return RedirectToPage(); // 重定向到当前页面,刷新列表
        }
    }
}

添加一些样式

为了让页面看起来更美观,我们可以添加一些 CSS。

打开 wwwroot/css/site.css 文件,在文件末尾添加以下样式:

/* wwwroot/css/site.css */
/* 为待办事项列表添加一些基本样式 */
.list-group-item {
    transition: background-color 0.2s;
}
.list-group-item-success {
    background-color: #d4edda;
    color: #155724;
}
.text-decoration-line-through {
    text-decoration: line-through;
    color: #6c757d;
}
.form-check-input:checked {
    background-color: #28a745;
    border-color: #28a745;
}

第五步:运行和测试

  1. 在 Visual Studio 中,确保选择了正确的启动项目(TodoWebApp)。
  2. 点击工具栏上的绿色“播放”按钮(或按 F5)。
  3. Visual Studio 会编译并启动应用程序,默认会在浏览器中打开 https://localhost:xxxx(xxxx 是一个随机端口号)。

测试功能:

  • 添加:在输入框中输入任务,点击“添加”按钮,列表应该会更新。
  • 标记完成:勾选任务前面的复选框,任务文本应该会被划掉,并且背景色会改变。
  • 删除:点击任务旁边的“删除”按钮,应该会弹出一个确认框,确认后任务会被移除。

第六步:发布和部署(可选)

当你准备将网站部署到服务器上时,可以执行以下步骤:

  1. 在 Visual Studio 中,右键点击项目解决方案资源管理器中的项目名称,选择“发布”。
  2. 选择“发布目标”,
    • Azure:直接发布到 Microsoft Azure App Service。
    • 文件夹:发布到本地的一个文件夹,你可以将这个文件夹中的所有文件通过 FTP/SFTP 上传到你的 Web 主机。
    • Docker:打包成 Docker 容器。
  3. 按照向导完成发布配置,系统会为你构建一个用于生产环境的优化版本。

恭喜!你已经成功创建并运行了一个功能完整的 ASP.NET Web 应用。

这个教程涵盖了 ASP.NET 核心开发的核心概念:

  • Razor Pages:用于构建页面的简单、直观的模型。
  • 模型:定义数据结构。
  • 服务:封装业务逻辑。
  • 依赖注入:将服务注入到页面中。
  • 表单处理:使用 asp-for[BindProperty] 来处理用户输入。
  • 路由@page 指令和 OnGet/OnPost 方法。

你可以基于这个项目继续扩展,

  • 使用 Entity Framework Core 和 SQL Server 替换内存中的列表。
  • 添加用户认证(登录/注册)。
  • 使用 Bootstrap 或 Tailwind CSS 美化界面。