ASP.NET 教程

ASP.NET 是微软推出的服务端 Web 开发框架,运行在 Windows 服务器(IIS)上,使用 C# 或 VB.NET 编写。它提供了从页面渲染、表单处理到数据库访问的完整解决方案。

ASP.NET 经历了多个技术路线,本教程按三大主流技术组织:

技术特点适用场景
Web Pages轻量、易上手,Razor 语法小型网站、入门学习
MVC关注点分离,可测试性强中型以上应用、团队协作
Web Forms控件驱动,事件模型传统企业应用、快速开发

开发环境

  • Visual Studio(推荐):微软官方 IDE
  • Visual Studio Code:轻量编辑器,配合 .NET SDK
  • .NET SDK:命令行工具,跨平台开发
💡 提示
现代 ASP.NET Core 已跨平台(Windows/Linux/macOS),本教程以经典 ASP.NET 概念为主线,部分示例同样适用于 Core 版本。

ASP.NET 简介

ASP.NET 由微软于 2002 年推出,用于构建动态网页与 Web 应用。它建立在 .NET Framework 之上,支持 C#、VB.NET 等语言。

核心特性

  • 服务器端执行:C#/VB 代码在服务器运行后生成 HTML 返回浏览器
  • 丰富的控件库:Button、GridView、Repeater 等,拖拽即用
  • 状态管理:ViewState、Session、Cookie 等机制
  • 安全机制:内置身份验证、授权、防注入

工作原理

浏览器 ──请求──▶ IIS(Web服务器)
                  │
                  ▼
             ASP.NET 引擎
                  │  执行 C#/VB 代码
                  │  读写数据库
                  ▼
浏览器 ◀─HTML响应─ 生成页面

三大技术对比

维度Web PagesMVCWeb Forms
页面模型单页脚本控制器+视图控件+事件
学习曲线低中中
控制 HTML完全完全部分(控件生成)
适合团队个人/小站专业团队快速交付
✅ 建议
新项目推荐 ASP.NET Core MVC;学习顺序建议:先 Web Pages 理解 Razor 语法,再学 MVC 掌握架构。

WebPages 简介

ASP.NET Web Pages 是 ASP.NET 中最轻量的一种开发模式:一个页面一个文件,混合 HTML 与服务器代码,用 Razor 语法(@ 开头)嵌入 C# 或 VB。

页面文件

网站目录/
├── Default.cshtml      # 首页
├── About.cshtml        # 关于页
├── _PageStart.cshtml   # 页面启动时执行(可选)
├── App_Data/           # 数据库文件
└── bin/                # 程序集

一个最简单的页面

<!DOCTYPE html>
<html>
<head><title>我的网站</title></head>
<body>
  <h1>欢迎访问</h1>
  <p>当前时间:@DateTime.Now</p>
  @{
    var name = "张三";
  }
  <p>你好,@name!</p>
</body>
</html>

运行方式

Web Pages 使用 WebMatrix 或 Visual Studio 开发,直接运行即可看到效果,无需编译整个项目。

💡 提示
.cshtml 表示 C# 语法页面,.vbhtml 表示 VB 语法页面。@ 后的代码在服务器执行,其余部分按普通 HTML 输出。

WebPages Razor

Razor 是 ASP.NET 的视图引擎语法,用 @ 符号在 HTML 中嵌入 C#/VB 代码,简洁直观。

基本语法

<!-- 输出变量 -->
<p>@name</p>

<!-- 代码块 -->
@{
  var total = 0;
  for (var i = 1; i <= 10; i++) { total += i; }
}

<!-- 表达式与调用 -->
<p>总和:@total</p>
<p>日期:@DateTime.Now.ToShortDateString()</p>

条件与循环

@if (User.IsLoggedIn)
{
  <p>欢迎回来,@User.Name</p>
}
else
{
  <p>请 <a href="/Login">登录</a></p>
}

<ul>
@foreach (var item in items)
{
  <li>@item.Name</li>
}
</ul>

布局引用

@{
  Layout = "~/_Layout.cshtml";
  Page.Title = "首页";
}

<p>页面正文内容……</p>
✅ 技巧
Razor 中 HTML 与代码通过 @ 切换;代码块内多行语句用 @{ ... } 包裹,单表达式直接 @expr。

WebPages 布局

布局页(Layout)定义网站的公共外观:页头、导航、页脚。内容页通过 Layout 属性引用布局,并用 RenderBody() 输出各自正文。

布局页 _Layout.cshtml

<!DOCTYPE html>
<html>
<head>
  <title>@Page.Title - 我的网站</title>
  <link rel="stylesheet" href="~/Content/Site.css">
</head>
<body>
  <header>
    <h1>我的网站</h1>
    <nav>
      <a href="~/">首页</a>
      <a href="~/About">关于</a>
    </nav>
  </header>

  <main>
    @RenderBody()    <!-- 内容页正文输出于此 -->
  </main>

  <footer>© @DateTime.Now.Year 我的网站</footer>
</body>
</html>

内容页引用布局

@{
  Layout = "~/_Layout.cshtml";
  Page.Title = "关于我们";
}

<h2>关于我们</h2>
<p>这里是网站介绍……</p>

多个布局 / 区域

@{
  Layout = "~/_AdminLayout.cshtml";   // 后台用独立布局
}

@{
  // 也可在 _PageStart.cshtml 统一设置默认布局
  Layout = "~/_Layout.cshtml";
}
✅ 最佳实践
公共头部/导航/页脚只写一次在布局页;每页用 Page.Title 等变量传递页面特有信息,避免重复代码。

WebPages 文件夹

Web Pages 项目按约定的文件夹结构组织文件,特殊文件夹有特定含义。理解这些约定是管理项目的基础。

标准文件夹

文件夹作用
/bin存放编译后的程序集(DLL)
/App_Data数据库文件(.mdf、.sdf 等),不可直接下载
/App_Code共享代码文件(.cshtml 的辅助类)
/ContentCSS、图片等静态资源
/ScriptsJavaScript 文件
/Shared布局页、部分视图等共享文件

示例结构

MySite/
├── Default.cshtml
├── About.cshtml
├── Shared/
│   └── _Layout.cshtml
├── App_Data/
│   └── SiteDB.sdf
├── Content/
│   └── Site.css
├── Scripts/
│   └── jquery.js
└── bin/
    └── Microsoft.Web.Infrastructure.dll
⚠️ 注意
App_Data 中的数据库文件不能被浏览器直接下载——放在该文件夹外的敏感文件(.config、.mdf)一定要移进来或配置访问拒绝。

WebPages 全局

全局文件用于在网站启动、每个请求开始时执行公共逻辑:初始化数据、设置布局、权限检查等。

_AppStart.cshtml —— 站点启动时

@{
  // 整个站点只执行一次
  App.SiteName = "我的知识库";
  App.Version = "1.0.0";
  App.StartTime = DateTime.Now;

  // 初始化数据库连接
  Database.Open("SiteDB");
}

_PageStart.cshtml —— 每个请求开始时

@{
  // 每页请求都会先执行这里
  Layout = "~/_Layout.cshtml";

  // 访问统计(简单示例)
  var counter = 0;
  if (Session["visit"] != null) counter = (int)Session["visit"];
  Session["visit"] = counter + 1;

  // 权限检查示例
  if (!WebSecurity.IsAuthenticated && Request.Url.AbsolutePath != "/Login")
  {
    Response.Redirect("~/Login");
  }
}

使用全局变量

@{
  var name = App.SiteName;          // 读取启动时设置的变量
}
<p>站点名称:@name</p>
<p>已启动:@App.StartTime</p>
💡 提示
文件名以下划线开头的页面(_AppStart、_PageStart、_Layout)不会直接被浏览器访问,只作为基础设施执行。

WebPages 表单

Web Pages 通过 Request.Form 或 Request["name"] 读取表单提交的数据,处理用户输入。

GET 与 POST

@{
  // 判断提交方式
  if (IsPost)
  {
    var name = Request.Form["username"];
    var email = Request["email"];   // 等价写法

    <p>收到:@name(@email)</p>
  }
}

<form method="post">
  <input type="text" name="username" placeholder="用户名">
  <input type="email" name="email" placeholder="邮箱">
  <button type="submit">提交</button>
</form>

查询字符串(GET 参数)

@{
  var id = Request.QueryString["id"];
  var page = Request["page"] ?? "1";   // 带默认值
}
<p>当前文章:@id,页码:@page</p>

输入校验

@{
  var error = "";
  if (IsPost)
  {
    var name = Request.Form["username"];
    if (name.IsEmpty())
    {
      error = "用户名不能为空";
    }
    else if (name.Length < 3)
    {
      error = "用户名至少 3 个字符";
    }
  }
}

@if (!error.IsEmpty())
{
  <p style="color:red">@error</p>
}
⚠️ 注意
所有用户输入都不可信!展示前用 @Html.Raw 要谨慎(防 XSS),数据库操作必须用参数化查询(防 SQL 注入)。

WebPages 对象

Web Pages 提供一系列内置对象,代表请求、响应、服务器、会话等,用于读取信息和控制输出。

核心对象速查

对象作用
Request客户端请求信息(表单、Cookie、URL)
Response服务器响应(写输出、跳转、设置 Cookie)
Server服务器信息与工具(编码、映射路径)
Session会话级数据(用户登录态等)
Application应用级共享数据
Database数据库访问辅助

常用示例

@{
  // 请求信息
  var url = Request.Url;                     // 完整地址
  var method = Request.HttpMethod;           // GET/POST
  var ip = Request.UserHostAddress;          // 客户端 IP

  // 响应控制
  Response.Redirect("~/Login");              // 跳转
  Response.StatusCode = 404;                 // 设置状态码

  // 会话
  Session["user"] = "admin";                 // 存
  var user = Session["user"];                // 取

  // 服务器
  var physicalPath = Server.MapPath("~/App_Data");
  var encoded = Server.HtmlEncode("<b>x</b>");
}

<p>你的 IP:@ip</p>
<p>编码后:@encoded</p>
✅ 技巧
输出用户提供的内容前,用 @Server.HtmlEncode(...) 或 Razor 默认的 @ 转义机制(@ 输出本身已转义 HTML),防止 XSS。

WebPages 文件

Web Pages 可以使用 .NET 的 File、Directory 类读写服务器文件,实现文本存储、日志、上传文件等。

写入文件

@{
  var path = Server.MapPath("~/App_Data/log.txt");
  File.WriteAllText(path, "内容:" + DateTime.Now);

  // 追加写入
  File.AppendAllText(path, "
" + "新的一行");
}

读取文件

@{
  var path = Server.MapPath("~/App_Data/data.txt");
  var content = "";
  if (File.Exists(path))
  {
    content = File.ReadAllText(path);
  }
}
<pre>@content</pre>

遍历目录

@{
  var dir = Server.MapPath("~/Content/images");
  var files = Directory.GetFiles(dir);
}
<ul>
@foreach (var f in files)
{
  var name = Path.GetFileName(f);
  <li>@name(@(new FileInfo(f).Length) 字节)</li>
}
</ul>

文件上传

@{
  if (IsPost)
  {
    var file = Request.Files["upload"];
    if (file != null && file.ContentLength > 0)
    {
      var savePath = Server.MapPath("~/Uploads/") + file.FileName;
      file.SaveAs(savePath);
    }
  }
}

<form method="post" enctype="multipart/form-data">
  <input type="file" name="upload">
  <button type="submit">上传</button>
</form>
⚠️ 注意
上传文件必须校验类型与大小,文件名要过滤危险字符,保存目录不能允许脚本执行,否则有安全风险。

WebPages 帮助器

帮助器(Helper)是封装了重复逻辑的代码块,可在多个页面复用,类似"函数"。Web Pages 内置多种帮助器,也支持自定义。

内置帮助器

帮助器用途
@WebGrid数据网格展示
@Chart图表绘制
@WebMail发送邮件
@WebSecurity登录、注册、角色
@Validation表单验证

创建自定义帮助器

在 App_Code 文件夹创建 MyHelpers.cshtml:

@helper ShowAlert(string message, string type = "info")
{
  var css = type == "error" ? "alert-error" : "alert-info";
  <div class="@css">@message</div>
}

@helper ShowList(string[] items)
{
  <ul>
  @foreach (var item in items)
  {
    <li>@item</li>
  }
  </ul>
}

使用自定义帮助器

@{
  var fruits = new[] { "苹果", "香蕉", "橘子" };
}

@MyHelpers.ShowAlert("保存成功!")
@MyHelpers.ShowAlert("出错了!", "error")
@MyHelpers.ShowList(fruits)
✅ 最佳实践
把重复的 UI 片段(提示框、列表、分页条)抽成帮助器,一处定义处处复用,减少页面冗余。

WebPages WebGrid

WebGrid 帮助器把数据渲染成带排序、分页的 HTML 表格,是 Web Pages 展示数据的利器。

基本用法

@{
  var db = Database.Open("SiteDB");
  var sql = "SELECT Id, Title, Price FROM Products";
  var data = db.Query(sql);

  var grid = new WebGrid(source: data, rowsPerPage: 10);
}

@grid.GetHtml(
  tableStyle: "grid",
  headerStyle: "head",
  alternatingRowStyle: "alt",
  columns: grid.Columns(
    grid.Column("Id", "编号"),
    grid.Column("Title", "标题"),
    grid.Column("Price", "价格", format: (item) => "¥" + item.Price)
  )
)

启用排序与分页

@{
  var grid = new WebGrid(source: data,
    rowsPerPage: 20,
    canSort: true,
    canPage: true,
    sortField: "Id",
    sortDirection: SortDirection.Ascending);
}

@grid.GetHtml(
  columns: grid.Columns(
    grid.Column("Id", "编号"),
    grid.Column("Title", "标题"),
    grid.Column(format: (item) =>
      Html.Raw("<a href='/Edit?id=" + item.Id + "'>编辑</a>"))
  )
)

添加分页导航

@grid.Pager(mode: WebGridPagerModes.All,
  firstText: "首页", previousText: "上一页",
  nextText: "下一页", lastText: "末页",
  numericLinksCount: 5)
💡 提示
WebGrid 数据源也可以是 List<dynamic> 或对象集合;列格式化用 format: 参数,item.字段 访问行数据。

WebPages 图表

Chart 帮助器在服务器端生成图表图片(柱状图、折线图、饼图等),适合报表页面。

柱状图

@{
  var chart = new Chart(width: 500, height: 300)
    .AddTitle("月度销售额")
    .AddSeries(
      name: "销售额",
      xValue: new[] { "1月", "2月", "3月", "4月" },
      yValues: new[] { "1200", "1800", "1500", "2400" },
      chartType: "Column")
    .Write();
}

从数据库取数绘图

@{
  var db = Database.Open("SiteDB");
  var data = db.Query("SELECT Month, Amount FROM Sales");

  var chart = new Chart(width: 600, height: 350)
    .AddTitle("销售趋势")
    .DataBindTable(dataSource: data, xField: "Month")
    .Write();
}

折线图与饼图

@{
  // 折线图
  var lineChart = new Chart(width: 500, height: 300)
    .AddTitle("访问趋势")
    .AddSeries(
      name: "访问量",
      xValue: new[] { "周一", "周二", "周三" },
      yValues: new[] { "300", "450", "520" },
      chartType: "Line")
    .Write();

  // 饼图
  var pieChart = new Chart(width: 400, height: 300)
    .AddTitle("市场份额")
    .AddSeries(
      name: "份额",
      xValue: new[] { "A", "B", "C" },
      yValues: new[] { "45", "35", "20" },
      chartType: "Pie")
    .Write();
}
✅ 技巧
用 chart.Save("~/App_Data/chart.png") 可以把图表存为图片文件,方便在邮件或文档中复用。

WebPages Email

WebMail 帮助器用于从 Web Pages 页面发送电子邮件,如注册确认、找回密码、通知邮件。

配置 SMTP

@{
  // 通常放在 _AppStart.cshtml 全局配置一次
  WebMail.SmtpServer = "smtp.example.com";
  WebMail.SmtpPort = 587;
  WebMail.EnableSsl = true;
  WebMail.UserName = "no-reply@example.com";
  WebMail.Password = "your-password";
  WebMail.From = "no-reply@example.com";
}

发送邮件

@{
  if (IsPost)
  {
    try
    {
      WebMail.Send(
        to: Request.Form["email"],
        subject: "注册确认",
        body: "感谢注册!请点击链接激活账号:http://example.com/activate",
        isBodyHtml: false
      );
      <p style="color:green">邮件已发送</p>
    }
    catch (Exception ex)
    {
      <p style="color:red">发送失败:@ex.Message</p>
    }
  }
}

<form method="post">
  <input type="email" name="email" placeholder="收件人邮箱">
  <button type="submit">发送</button>
</form>

发送 HTML 邮件

@{
  WebMail.Send(
    to: "user@example.com",
    subject: "欢迎使用",
    body: "<h1>欢迎!</h1><p>点击 <a href='http://example.com'>这里</a> 开始</p>",
    isBodyHtml: true
  );
}
⚠️ 注意
SMTP 密码不要写死在页面,应放在 Web.config 或环境变量中;生产环境建议使用企业邮件服务(如腾讯云、阿里云 SES)。

WebPages PHP

Web Pages 支持在页面中使用 PHP 辅助功能——事实上本教程这一节介绍的是 Web Pages 中与 PHP 类似的概念对照,帮助有 PHP 背景的开发者快速迁移。

概念对照

PHPASP.NET Web Pages
<?php ... ?>@{ ... } 或 @
$_POST['x']Request.Form["x"]
$_GET['x']Request.QueryString["x"]
mysqli_connect()Database.Open("conn")
session_start()Session(默认可用)
include 'x.php'@RenderPage("x.cshtml")

PHP 代码示例迁移

@{
  // PHP: $name = $_POST['name'] ?? '';
  var name = Request.Form["name"] ?? "";

  // PHP: if ($_SERVER['REQUEST_METHOD'] == 'POST')
  if (IsPost)
  {
    // PHP: echo "Hello " . $name;
    <p>Hello @name</p>
  }
}

包含公共页面

@{
  // PHP: include 'header.php';
  @RenderPage("~/Shared/_Header.cshtml")

  // 页面主体……
  <p>内容区</p>

  // PHP: include 'footer.php';
  @RenderPage("~/Shared/_Footer.cshtml")
}
💡 提示
有 PHP 经验的开发者迁移到 ASP.NET,重点是记住三个对应关系:$_POST/$_GET → Request、echo → @、include → @RenderPage。

WebPages 发布

开发完成后,把 Web Pages 网站部署到服务器(IIS)即可对外提供服务。本教程介绍从准备到上线的完整流程。

发布前检查

  • 确认 App_Data 中有数据库文件(如 .sdf)
  • 检查 Web.config 连接字符串
  • 将调试信息关闭:<compilation debug="false">
  • 移除敏感信息(密码、密钥)

Web.config 连接字符串

<configuration>
  <connectionStrings>
    <add name="SiteDB"
         connectionString="Data Source=|DataDirectory|SiteDB.sdf"
         providerName="System.Data.SqlServerCe.4.0" />
  </connectionStrings>
  <system.web>
    <compilation debug="false" targetFramework="4.8" />
  </system.web>
</configuration>

部署到 IIS 步骤

1. 服务器安装 IIS 与 .NET Framework
2. 在 IIS 创建网站,绑定域名/IP 与端口
3. 设置物理路径为网站目录(含 Default.cshtml)
4. 给网站目录授予 IIS_IUSRS 读取权限
5. 为 App_Data 授予读写权限(数据库需要)
6. 浏览器访问验证

数据库文件权限

# Windows 下给 App_Data 授权(管理员 PowerShell)
icacls "C:\inetpub\wwwroot\MySite\App_Data" /grant "IIS_IUSRS:(OI)(CI)M"
⚠️ 注意
Web Pages 使用 SQL Server Compact(.sdf)文件数据库时,IIS 进程需要对该文件有读写权限,否则会出现数据库打开失败。

WebPages 实例

本教程综合运用前面所学,构建一个简单的博客:列表展示 + 详情页 + 新增文章。

1. 布局页 Shared/_Layout.cshtml

<!DOCTYPE html>
<html>
<head>
  <title>@Page.Title - 我的博客</title>
</head>
<body>
  <header><h1>📝 我的博客</h1></header>
  <main>@RenderBody()</main>
  <footer>© @DateTime.Now.Year</footer>
</body>
</html>

2. 首页 Default.cshtml —— 文章列表

@{
  Layout = "~/_Layout.cshtml";
  Page.Title = "首页";

  var db = Database.Open("BlogDB");
  var posts = db.Query("SELECT Id, Title, Date FROM Posts ORDER BY Date DESC");
}

<ul>
@foreach (var p in posts)
{
  <li>
    <a href="~/Post?id=@p.Id">@p.Title</a>
    <small>@p.Date</small>
  </li>
}
</ul>
<a href="~/AddPost">写文章</a>

3. 新增文章 AddPost.cshtml

@{
  Layout = "~/_Layout.cshtml";
  Page.Title = "写文章";

  if (IsPost)
  {
    var title = Request.Form["title"];
    var body = Request.Form["body"];
    var db = Database.Open("BlogDB");
    db.Execute("INSERT INTO Posts (Title, Body, Date) VALUES (@0, @1, @2)",
      title, body, DateTime.Now);
    Response.Redirect("~/");
  }
}

<form method="post">
  <input type="text" name="title" placeholder="标题">
  <textarea name="body" rows="8" placeholder="正文"></textarea>
  <button type="submit">发布</button>
</form>
✅ 练习
试着给它加上:文章详情页(通过 Request.QueryString["id"] 查询)、编辑/删除功能、简单登录。

WebPages 类

在 Web Pages 中编写自定义类,把业务逻辑封装为可复用对象。类文件放在 App_Code 文件夹。

创建类文件 App_Code/Product.cs

using System;
using System.Collections.Generic;

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public string GetDisplay()
    {
        return Name + " - ¥" + Price;
    }

    public static List<Product> GetSampleData()
    {
        return new List<Product> {
            new Product { Id = 1, Name = "手机", Price = 1999 },
            new Product { Id = 2, Name = "耳机", Price = 399 }
        };
    }
}

在页面中使用类

@{
  var products = Product.GetSampleData();
}

<ul>
@foreach (var p in products)
{
  <li>@p.GetDisplay()</li>
}
</ul>

结合数据库的类

@{
  var db = Database.Open("SiteDB");
  var rows = db.Query("SELECT * FROM Products");

  var products = new List<Product>();
  foreach (var r in rows)
  {
    products.Add(new Product {
      Id = r.Id,
      Name = r.Name,
      Price = r.Price
    });
  }
}

<table>
  <tr><th>ID</th><th>名称</th><th>价格</th></tr>
@foreach (var p in products)
{
  <tr><td>@p.Id</td><td>@p.Name</td><td>@p.Price</td></tr>
}
</table>
💡 提示
类文件放入 App_Code 后会被自动编译,页面中可直接使用,无需额外引用;若类较多,建议改用正式类库项目。

WebPages 安全

Web Pages 提供 WebSecurity 帮助器实现注册、登录、角色等安全功能,是搭建带账号系统网站的基础。

初始化(通常放 _AppStart.cshtml)

@{
  WebSecurity.InitializeDatabaseConnection(
    connectionStringName: "SiteDB",
    userTableName: "UserProfile",
    userIdColumn: "UserId",
    userNameColumn: "UserName",
    autoCreateTables: true
  );
}

注册与登录

@{
  // 注册
  if (IsPost && Request.Form["action"] == "register")
  {
    var email = Request.Form["email"];
    var password = Request.Form["password"];
    if (WebSecurity.UserExists(email) == false)
    {
      WebSecurity.CreateUserAndAccount(email, password);
      WebSecurity.Login(email, password);
      Response.Redirect("~/");
    }
  }

  // 登录
  if (IsPost && Request.Form["action"] == "login")
  {
    if (WebSecurity.Login(
        Request.Form["email"],
        Request.Form["password"],
        persistCookie: true))
    {
      Response.Redirect("~/Member");
    }
  }
}

角色与权限检查

@{
  // 创建角色并分配
  if (!Roles.RoleExists("Admin")) Roles.CreateRole("Admin");
  Roles.AddUserToRole("admin@example.com", "Admin");

  // 检查权限
  if (WebSecurity.IsAuthenticated)
  {
    <p>已登录:@WebSecurity.CurrentUserName</p>
  }

  if (Roles.IsUserInRole(WebSecurity.CurrentUserName, "Admin"))
  {
    <a href="~/Admin">管理后台</a>
  }
}

// 退出登录
// @{ WebSecurity.Logout(); }
⚠️ 注意
密码由 WebSecurity 自动加盐哈希存储,不要自己明文保存;重要操作(改密、删号)前必须二次验证。

WebPages 数据库

Web Pages 使用 Database 帮助器访问数据库,支持 SQL Server、SQL Server Compact、MySQL 等。

打开数据库

@{
  // 用 Web.config 中的连接字符串
  var db = Database.Open("SiteDB");

  // 或直接指定路径
  // var db = Database.Open("~/App_Data/SiteDB.sdf");
}

查询数据

@{
  var db = Database.Open("SiteDB");

  // 多条
  var products = db.Query("SELECT * FROM Products WHERE Price > @0", 100);

  // 单条
  var first = db.QuerySingle("SELECT * FROM Products WHERE Id = @0", 1);

  // 标量值
  var count = db.QueryValue("SELECT COUNT(*) FROM Products");
}

@foreach (var p in products)
{
  <p>@p.Name - @p.Price</p>
}
<p>总数:@count</p>

增删改(参数化)

@{
  var db = Database.Open("SiteDB");

  // 插入
  db.Execute("INSERT INTO Products (Name, Price) VALUES (@0, @1)",
    "键盘", 99);

  // 更新
  db.Execute("UPDATE Products SET Price = @0 WHERE Id = @1",
    129, 3);

  // 删除
  db.Execute("DELETE FROM Products WHERE Id = @0", 5);
}

表格展示

@{
  var rows = db.Query("SELECT * FROM Products ORDER BY Price DESC");
}

<table>
  <tr><th>名称</th><th>价格</th></tr>
@foreach (var r in rows)
{
  <tr><td>@r.Name</td><td>@r.Price</td></tr>
}
</table>
⚠️ 注意
永远使用参数化查询(@0、@1)拼接条件,绝不直接拼字符串,否则极易被 SQL 注入。

WebPages 邮局

本节标题中的「邮局」对应 Web Pages 邮件相关功能的补充:发送带附件、多收件人、模板邮件等进阶用法。

多收件人

@{
  WebMail.Send(
    to: "a@example.com; b@example.com",   // 分号分隔多收件人
    subject: "通知",
    body: "批量通知内容……"
  );
}

发送附件

@{
  var attachment = Server.MapPath("~/App_Data/report.pdf");
  WebMail.Send(
    to: "user@example.com",
    subject: "月度报表",
    body: "请查收附件",
    filesToAttach: new[] { attachment }
  );
}

使用模板发送 HTML 邮件

@{
  // 模板文件 EmailTemplates/Welcome.cshtml
  var templatePath = Server.MapPath("~/EmailTemplates/Welcome.cshtml");
  var body = File.ReadAllText(templatePath)
    .Replace("@Name", "张三")
    .Replace("@Link", "http://example.com/activate?id=123");

  WebMail.Send(
    to: "user@example.com",
    subject: "欢迎加入",
    body: body,
    isBodyHtml: true
  );
}

防止重复发送

@{
  // 用 Session 标记已发送,避免表单刷新重复发
  if (Session["mailSent"] == null)
  {
    WebMail.Send(to: "user@example.com", subject: "验证码",
                 body: "您的验证码是:" + GenerateCode());
    Session["mailSent"] = DateTime.Now;
  }
}

@helper GenerateCode()
{
  @(new Random().Next(100000, 999999).ToString())
}
✅ 建议
发送邮件建议异步化:把邮件任务放入队列(如 Redis 队列、后台作业),避免用户等待 SMTP 响应。

Razor 简介

Razor 是 ASP.NET 的标记语法(视图引擎),让你在 HTML 中直接用 C#/VB 编写服务端逻辑。它是 MVC 与 Web Pages 的共用基础。

为什么用 Razor

  • 简洁:@ 一个字符切换代码模式
  • 智能提示:Visual Studio 中代码高亮与补全
  • HTML 友好:与 HTML 标签自然混写
  • 自动转义:默认转义 HTML,防 XSS

Razor 文件类型

文件引擎用途
.cshtmlC# RazorMVC 视图、Web Pages
.vbhtmlVB RazorVB 版本视图

最小 Razor 页面

@{
  var message = "Hello, Razor!";
  var now = DateTime.Now;
}

<!DOCTYPE html>
<html>
<body>
  <h1>@message</h1>
  <p>当前时间:@now</p>
  <p>明天是:@now.AddDays(1).ToShortDateString()</p>
</body>
</html>

代码块与表达式

@{                    // 多语句代码块
  var x = 10;
  var y = 20;
  var sum = x + y;
}
<p>@sum</p>           <!-- 单表达式 -->
<p>@(x * y)</p>       <!-- 括号内表达式 -->
💡 提示
Razor 的核心思想:HTML 是主体,@ 标记代码。遇到复杂逻辑用代码块 @{},简单输出用 @表达式。

Razor 语法

掌握 Razor 的核心语法规则是编写视图的基础。本教程覆盖注释、转义、循环、条件、布局等常用语法。

注释

@* 这是 Razor 注释,不会输出到 HTML *@

<!-- 这是 HTML 注释,会输出到浏览器 -->

输出字面 @ 符号

<p>邮箱用 @@ 输出 @ 符号:contact@@example.com</p>

条件语句

@if (score >= 60)
{
  <p>及格</p>
}
else if (score >= 80)
{
  <p>优秀</p>
}
else
{
  <p>不及格</p>
}

循环语句

<ul>
@for (var i = 1; i <= 5; i++)
{
  <li>第 @i 项</li>
}
</ul>

@foreach (var item in Model.Items)
{
  <p>@item.Name</p>
}

@while (n > 0)
{
  <span>@n </span>
  n--;
}

try-catch 与 switch

@try
{
  @ViewBag.Result
}
catch (Exception ex)
{
  <p style="color:red">出错了:@ex.Message</p>
}

@switch (status)
{
  case 1: <p>启用</p>; break;
  case 0: <p>停用</p>; break;
  default: <p>未知</p>; break;
}
✅ 技巧
Razor 会自动把 @ 后的代码与 HTML 区分开;当表达式与文字连写有歧义时,用 @(表达式) 明确边界。

Razor C# 变量

Razor 视图中可以使用 C# 的变量与数据类型。本教程整理常见类型的声明、转换与输出。

声明变量

@{
  var name = "张三";              // string
  var age = 25;                   // int
  var price = 99.5;               // double
  var isVip = true;               // bool
  var tags = new[] { "A", "B" };  // string[]
  var user = new { Name = "李四", Age = 30 };  // 匿名类型
}

显示类型与输出

@{
  var x = 10;
  var y = 3;
  var result = x / (double)y;      // 3.333...
}

<p>@name 今年 @age 岁</p>
<p>结果:@result.ToString("F2")</p>
<p>类型:@x.GetType().Name</p>

类型转换

@{
  var numStr = "42";
  var num = numStr.AsInt();        // 字符串 → int

  var val = 3.14;
  var intVal = val.AsInt();        // double → int

  var dateStr = "2026-08-17";
  var date = dateStr.AsDateTime(); // 字符串 → DateTime

  // 安全转换(失败给默认值)
  var safe = Request.Form["id"].AsInt(0);
}

字符串操作

@{
  var full = "Hello" + " " + "World";
  var upper = full.ToUpper();
  var sub = full.Substring(0, 5);
  var len = full.Length;
}

<p>@upper</p>
<p>前5个字符:@sub,长度:@len</p>
💡 提示
Razor 中的 var 由编译器推断类型;从 Request.Form 取出的值都是字符串,运算前要 AsInt() 等转换。

Razor C# 循环

循环用于重复输出列表、表格等。Razor 支持 for、foreach、while、do-while 全部 C# 循环。

for 循环

<table>
@for (var i = 1; i <= 10; i++)
{
  <tr><td>@i × 3 = @(i * 3)</td></tr>
}
</table>

foreach 循环(最常用)

@{
  var fruits = new[] { "苹果", "香蕉", "橘子", "葡萄" };
}

<ul>
@foreach (var fruit in fruits)
{
  <li>@fruit</li>
}
</ul>

遍历数据集合

@{
  var db = Database.Open("SiteDB");
  var products = db.Query("SELECT * FROM Products");
}

<table>
  <tr><th>名称</th><th>价格</th></tr>
@foreach (var p in products)
{
  <tr><td>@p.Name</td><td>¥@p.Price</td></tr>
}
</table>

带索引的循环

@{
  var list = new[] { "A", "B", "C" };
  for (var i = 0; i < list.Length; i++)
  {
    <p>第 @(i + 1) 项:@list[i]</p>
  }
}

while 与 break/continue

@{
  var n = 0;
  while (n < 10)
  {
    n++;
    if (n % 2 == 0) continue;   // 跳过偶数
    <span>@n </span>            // 输出奇数
    if (n >= 7) break;          // 到 7 停止
  }
}
✅ 最佳实践
遍历数据优先 foreach(无需关心索引);需要序号或按索引访问时用 for;break 跳出、continue 跳过本轮。

Razor C# 逻辑

逻辑判断让视图根据数据动态渲染不同内容。Razor 使用 C# 的 if/else、switch 与逻辑运算符。

if / else if / else

@{
  var score = 85;
}

@if (score >= 90)
{
  <p>优秀 🏆</p>
}
else if (score >= 60)
{
  <p>及格 ✅</p>
}
else
{
  <p>不及格 ❌</p>
}

逻辑运算符

@{
  var age = 25;
  var hasTicket = true;
  var isMember = false;
}

@if (age >= 18 && hasTicket)
{
  <p>可以入场(成年且购票)</p>
}

@if (isMember || age <= 12)
{
  <p>享受优惠</p>
}

@if (!hasTicket)
{
  <p>请先购票</p>
}

三元表达式

@{
  var n = 7;
}
<p>@(n % 2 == 0 ? "偶数" : "奇数")</p>

switch 语句

@{
  var status = "pending";
}

@switch (status)
{
  case "pending":
    <p>待处理</p>
    break;
  case "done":
    <p>已完成</p>
    break;
  default:
    <p>未知状态</p>
    break;
}

判断 null 与空

@{
  var title = ViewBag.Title as string;

  if (title.IsEmpty())       // 空字符串或 null
  {
    title = "默认标题";
  }

  if (Model != null)
  {
    <p>@Model.Name</p>
  }
}
💡 提示
逻辑判断中务必考虑空值:IsEmpty() 同时处理 null 与空字符串;从数据库取出的可能为 null 的字段,输出前先判空。

Razor VB 变量

Razor 同样支持 VB.NET 语法(.vbhtml 文件),适合习惯 Basic 语法的开发者。本教程用 VB 演示变量与运算。

声明变量(VB)

@Code
  Dim name As String = "张三"
  Dim age As Integer = 25
  Dim price As Double = 99.5
  Dim isVip As Boolean = True
  Dim tags() As String = {"A", "B", "C"}
End Code

输出(VB)

@Code
  Dim x As Integer = 10
  Dim y As Integer = 20
End Code

<p>@name 今年 @age 岁</p>
<p>总和:@(x + y)</p>
<p>拼接:@(name & " 你好")</p>

类型转换(VB)

@Code
  Dim numStr As String = "42"
  Dim num As Integer = CInt(numStr)     ' 字符串 → int

  Dim val As Double = 3.14
  Dim intVal As Integer = CInt(val)     ' double → int

  Dim dateStr As String = "2026-08-17"
  Dim dt As DateTime = CDate(dateStr)   ' 字符串 → DateTime
End Code

条件与循环(VB)

@Code
  Dim score As Integer = 85

  If score >= 90 Then
    @<b>优秀</b>
  ElseIf score >= 60 Then
    @<b>及格</b>
  Else
    @<b>不及格</b>
  End If

  For i As Integer = 1 To 5
    @<span>@i </span>
  Next
End Code
✅ 建议
VB 在字符串拼接用 &、代码块用 @Code ... End Code、注释用 '——团队内保持语言统一,避免混用。

Razor VB 逻辑

用 VB 语法实现逻辑判断。VB 的逻辑运算符与 C# 略有不同:用 And/Or/Not 替代 &&/||/!。

if 逻辑(VB)

@Code
  Dim age As Integer = 25
  Dim hasTicket As Boolean = True
  Dim isMember As Boolean = False
End Code

@If age >= 18 And hasTicket Then
  <p>可以入场(成年且购票)</p>
End If

@If isMember Or age <= 12 Then
  <p>享受优惠</p>
End If

@If Not hasTicket Then
  <p>请先购票</p>
End If

Select Case 分支

@Code
  Dim status As String = "done"
End Code

@Select Case status
  Case "pending"
    @<p>待处理</p>
  Case "done"
    @<p>已完成</p>
  Case Else
    @<p>未知状态</p>
End Select

三元与 IIF

@Code
  Dim n As Integer = 7
  Dim result As String = IIf(n Mod 2 = 0, "偶数", "奇数")
End Code

<p>@result</p>

<!-- 内联条件输出 -->
<p>@(If(n > 5, "大数", "小数"))</p>

循环与退出(VB)

@Code
  For i As Integer = 1 To 10
    If i Mod 2 = 0 Then Continue For   ' 跳过偶数
    @<span>@i </span>
    If i >= 7 Then Exit For            ' 到 7 退出
  Next

  Dim list As String() = {"苹果", "香蕉", "橘子"}
  For Each f As String In list
    @<li>@f</li>
  Next
End Code
💡 提示
VB 关键差异速记:And/Or/Not、If...Then...End If、Select Case、IIf()、Continue For / Exit For。

MVC 简介

ASP.NET MVC 是一种 Web 开发架构模式,把应用拆分为 Model(模型)/ View(视图)/ Controller(控制器) 三层,职责清晰、可测试性强。

三层职责

层职责示例
Model数据与业务逻辑Product 类、数据库访问
View展示界面(HTML)Index.cshtml、Details.cshtml
Controller接收请求、调度HomeController、ProductController

请求流程

浏览器
  │ 请求 /Product/Index
  ▼
路由(Route)  → 找到 ProductController.Index()
  ▼
Controller   → 读取 Model 数据
  ▼
View         → 渲染 HTML
  ▼
浏览器 收到页面

MVC vs Web Forms

维度MVCWeb Forms
URL语义化 /Product/1页面文件 .aspx
HTML 控制完全控制控件生成
状态管理无 ViewStateViewState 自动
可测试性高(依赖注入)低

第一个控制器

public class HomeController : Controller
{
    public ActionResult Index()
    {
        ViewBag.Message = "欢迎使用 MVC";
        return View();
    }
}
✅ 建议
现代 ASP.NET Core 默认使用 MVC 风格。学习 MVC 的核心是理解路由 → 控制器 → 视图的请求链路。

MVC 应用程序

创建并了解一个 ASP.NET MVC 应用的项目结构与生命周期。

创建项目

# 使用 .NET CLI 创建 MVC 项目
dotnet new mvc -n MyMvcApp
cd MyMvcApp
dotnet run

项目结构

MyMvcApp/
├── Controllers/          # 控制器
│   ├── HomeController.cs
│   └── ProductsController.cs
├── Models/               # 模型类
│   └── Product.cs
├── Views/                # 视图
│   ├── Home/
│   │   ├── Index.cshtml
│   │   └── About.cshtml
│   ├── Products/
│   │   └── Index.cshtml
│   ├── Shared/
│   │   └── _Layout.cshtml
│   └── _ViewStart.cshtml
├── wwwroot/              # 静态资源
│   ├── css/
│   ├── js/
│   └── images/
├── Program.cs            # 应用入口
└── appsettings.json      # 配置

入口与路由(Program.cs)

var builder = WebApplication.CreateBuilder(args);

// 注册 MVC 服务
builder.Services.AddControllersWithViews();

var app = builder.Build();

// 静态文件
app.UseStaticFiles();

// 默认路由映射
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
💡 提示
约定优于配置:控制器放 Controllers、视图按「控制器名/动作名」放 Views,框架会自动匹配,减少配置量。

MVC 文件夹

MVC 项目依赖约定式文件夹组织代码,理解每个文件夹的作用能帮你快速定位与扩展。

核心文件夹

文件夹内容
/Controllers控制器类(处理请求)
/Models模型类与 ViewModel
/Views视图模板(.cshtml)
/Views/Shared布局、分部视图、错误页
/wwwrootCSS/JS/图片等静态资源
/Data 或 /Repository数据访问层(可选)

Views 目录约定

Views/
├── _ViewStart.cshtml      # 所有视图默认布局
├── _ViewImports.cshtml    # 全局 using
├── Home/
│   ├── Index.cshtml       # HomeController.Index
│   └── About.cshtml       # HomeController.About
├── Products/
│   └── Index.cshtml       # ProductsController.Index
└── Shared/
    ├── _Layout.cshtml     # 布局页
    └── Error.cshtml       # 错误页

区域(Areas,大型项目)

Areas/
├── Admin/
│   ├── Controllers/
│   ├── Models/
│   └── Views/
├── Shop/
│   ├── Controllers/
│   ├── Models/
│   └── Views/
✅ 建议
简单项目保持默认结构即可;大型项目按功能模块拆 Areas(Admin/Shop/Report),比按技术层拆分更易维护。

MVC 布局

MVC 视图通过布局页(Layout)统一外观,与 Web Pages 的 Layout 类似,但使用 _ViewStart.cshtml 全局配置。

布局页 _Layout.cshtml

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>@ViewBag.Title - 我的站点</title>
  <link rel="stylesheet" href="~/css/site.css">
  @RenderSection("Styles", required: false)
</head>
<body>
  <header>
    <nav>@Html.ActionLink("首页", "Index", "Home")</nav>
  </header>

  <main>
    @RenderBody()          <!-- 视图主体 -->
  </main>

  <footer>© @DateTime.Now.Year</footer>
  @RenderSection("Scripts", required: false)
</body>
</html>

全局默认布局

@* Views/_ViewStart.cshtml —— 所有视图默认使用 *@
@{
    Layout = "_Layout";
}

视图中使用布局

@{
    ViewBag.Title = "产品列表";
}

<h2>产品列表</h2>
<ul>
@foreach (var p in Model)
{
  <li>@p.Name - @p.Price</li>
}
</ul>

@section Scripts {
  <script>console.log("页面专用脚本");</script>
}

局部视图(RenderPartial)

@* 局部视图 _ProductCard.cshtml *@
<div class="card">
  <h3>@Model.Name</h3>
  <p>¥@Model.Price</p>
</div>

@* 主视图中循环渲染局部视图 *@
@foreach (var p in Model)
{
  @Html.Partial("_ProductCard", p)
}
✅ 最佳实践
公共资源放布局页,页面独有资源用 @section;复用组件用局部视图 + 强类型模型,保持视图整洁。

MVC 控制器

控制器是 MVC 的核心:接收用户请求、协调模型与视图。每个公开方法是一个「动作(Action)」,通过 URL 调用。

控制器与动作

public class ProductsController : Controller
{
    // GET /Products          → 列表
    public IActionResult Index()
    {
        var list = GetProducts();
        return View(list);
    }

    // GET /Products/Details/5 → 详情
    public IActionResult Details(int id)
    {
        var p = GetProduct(id);
        if (p == null) return NotFound();
        return View(p);
    }
}

动作返回值类型

返回说明
View()渲染视图
RedirectToAction()跳转到其他动作
Json(data)返回 JSON(AJAX)
Content(str)返回纯文本
NotFound()404
File(...)文件下载

接收参数

// 路由参数 /Products/Details/5
public IActionResult Details(int id) { ... }

// 查询字符串 /Products?page=2&size=20
public IActionResult Index(int page = 1, int size = 20) { ... }

// 表单提交(模型绑定)
[HttpPost]
public IActionResult Create(Product model)
{
    if (ModelState.IsValid)
    {
        Save(model);
        return RedirectToAction("Index");
    }
    return View(model);
}

路由特性(Attribute Routing)

[Route("products")]
public class ProductsController : Controller
{
    [HttpGet("")]
    public IActionResult Index() { ... }

    [HttpGet("{id:int}")]
    public IActionResult Details(int id) { ... }

    [HttpPost("")]
    public IActionResult Create(Product model) { ... }
}
💡 提示
控制器保持薄:只做「接收请求→调用服务→返回视图」,业务逻辑放模型或服务层,便于单元测试。

MVC 视图

视图(View)负责展示数据。MVC 视图是强类型化的:通过 @model 声明数据类型,获得编译期检查与智能提示。

强类型视图

@* Views/Products/Index.cshtml *@
@model IEnumerable<Product>

<h2>产品列表</h2>
<table>
  <tr><th>名称</th><th>价格</th></tr>
@foreach (var p in Model)
{
  <tr><td>@p.Name</td><td>@p.Price</td></tr>
}
</table>

ViewBag 与 ViewData

// 控制器
ViewBag.Title = "首页";
ViewData["Count"] = 42;

// 视图
<h1>@ViewBag.Title</h1>
<p>共 @ViewData["Count"] 条</p>

HTML 帮助器

@* 表单生成 *@
@using (Html.BeginForm("Create", "Products"))
{
  @Html.TextBox("name")              // <input name="name">
  @Html.Password("pwd")              // 密码框
  @Html.DropDownList("catId", items) // 下拉框
  @Html.TextArea("desc", rows: 4)    // 多行文本
  <button type="submit">保存</button>
}

@* 链接 *@
@Html.ActionLink("详情", "Details", "Products", new { id = 5 })

Razor 标签帮助器(Core)

@* ASP.NET Core 标签帮助器更简洁 *@
<form asp-action="Create" asp-controller="Products" method="post">
  <input asp-for="Name">
  <select asp-for="CategoryId" asp-items="ViewBag.Categories"></select>
  <button type="submit">保存</button>
</form>
✅ 建议
视图里只做展示与简单逻辑;复杂计算放模型或服务;数据量大的页面用 @Html.Partial 拆分局部视图。

MVC 数据库

MVC 应用中操作数据库的主流方式是 EF Core(Entity Framework Core)——微软的 ORM,用 C# 对象映射数据库表。

安装与配置

# 安装 EF Core 与 SQL Server 提供程序
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

# 或 MySQL 提供程序
dotnet add package Pomelo.EntityFrameworkCore.MySql

定义模型与上下文

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    public DbSet<Product> Products { get; set; }
}

注册服务(Program.cs)

var connection = builder.Configuration
    .GetConnectionString("Default");

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connection));

控制器中使用 EF

public class ProductsController : Controller
{
    private readonly AppDbContext _db;

    public ProductsController(AppDbContext db)
    {
        _db = db;   // 依赖注入
    }

    public IActionResult Index()
    {
        var list = _db.Products
            .OrderBy(p => p.Price)
            .ToList();
        return View(list);
    }

    [HttpPost]
    public IActionResult Create(Product p)
    {
        _db.Products.Add(p);
        _db.SaveChanges();     // 提交到数据库
        return RedirectToAction("Index");
    }
}

迁移(Migration)

dotnet ef migrations add InitialCreate
dotnet ef database update
⚠️ 注意
EF 查询默认延迟执行(IQueryable),用到 ToList() 才真正查库;连接字符串放 appsettings.json,生产环境用环境变量覆盖。

MVC 模型

模型(Model)承载数据与业务规则,配合数据注解(Data Annotations)实现自动验证。

模型类与数据注解

using System.ComponentModel.DataAnnotations;

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

    [Required(ErrorMessage = "名称不能为空")]
    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; }

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

    [Display(Name = "库存")]
    public int Stock { get; set; }

    [EmailAddress]
    public string ContactEmail { get; set; }
}

常用验证特性

特性作用
[Required]必填
[StringLength(max)]最大长度
[Range(min, max)]数值范围
[EmailAddress]邮箱格式
[RegularExpression]正则校验
[Compare("字段")]两字段一致(确认密码)
[DataType(DataType.Date)]日期类型提示

控制器中验证

[HttpPost]
public IActionResult Create(Product model)
{
    if (!ModelState.IsValid)
    {
        // 校验失败,返回视图并显示错误
        return View(model);
    }
    _db.Products.Add(model);
    _db.SaveChanges();
    return RedirectToAction("Index");
}

视图中显示错误

@model Product

<form asp-action="Create" method="post">
  <label asp-for="Name">名称</label>
  <input asp-for="Name">
  <span asp-validation-for="Name" class="text-danger"></span>
  <br>

  <label asp-for="Price">价格</label>
  <input asp-for="Price">
  <span asp-validation-for="Price" class="text-danger"></span>

  <button type="submit">保存</button>
</form>

@section Scripts {
  @* 客户端即时验证(需引用 jquery.validation) *@
  @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
✅ 最佳实践
表单校验前端体验 + 后端兜底双管齐下:数据注解在服务端强制执行,前端脚本提供即时反馈。

MVC 安全

MVC 应用的安全基线:身份验证、授权、防 CSRF、防 XSS、防 SQL 注入。ASP.NET Core 内置了大部分机制,关键是正确启用。

身份验证与授权

// Program.cs 注册认证
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options => {
        options.LoginPath = "/Account/Login";
    });

builder.Services.AddAuthorization();

// 控制器/动作上声明权限
[Authorize]                    // 需要登录
public class AdminController : Controller { }

[Authorize(Roles = "Admin")]   // 需要指定角色
public class AdminController : Controller { }

防 CSRF(跨站请求伪造)

@* 表单中自动带上防伪令牌 *@
<form asp-action="Delete" method="post">
  @Html.AntiForgeryToken()      <!-- 生成 hidden 令牌 -->
  <button type="submit">删除</button>
</form>
[HttpPost]
[ValidateAntiForgeryToken]      // 验证令牌
public IActionResult Delete(int id) { ... }

防 XSS(默认已转义)

@* Razor 默认转义所有输出,以下安全 *@
<p>@userInput</p>

@* 只有当确实需要渲染 HTML 时才用 Raw(非常谨慎) *@
@Html.Raw(trustedContent)

防 SQL 注入(用 EF/LINQ)

// 正确:参数化
var list = _db.Products
    .Where(p => p.Name.Contains(keyword))
    .ToList();

// 危险:字符串拼接 SQL(禁止)
// var sql = "SELECT * FROM Products WHERE Name LIKE '%" + keyword + "%'";

敏感配置加密

# 连接字符串/密钥不放源码库,用环境变量或密钥管理
export ConnectionStrings__Default="Server=...;Password=..."
dotnet run
⚠️ 注意
安全是纵深防御:登录、角色、防伪令牌、输入校验、HTTPS 一起上,缺一不可;上线前至少做一次安全扫描。

MVC HTML 帮助器

HTML 帮助器在视图中生成常用 HTML 元素,让代码更简洁、安全(自动编码)。经典 ASP.NET MVC 与 Core 的写法略有差异,本教程两者都介绍。

链接与表单(经典)

@* 链接 *@
@Html.ActionLink("首页", "Index", "Home")
@Html.ActionLink("详情", "Details", "Products", new { id = 5 }, null)

@* 表单 *@
@using (Html.BeginForm("Create", "Products", FormMethod.Post))
{
  <p>
    @Html.Label("name", "名称:")
    @Html.TextBox("name")
    @Html.ValidationMessage("name")
  </p>
  <p>
    @Html.Label("price", "价格:")
    @Html.TextBox("price", null, new { type = "number", step = "0.01" })
  </p>
  <button type="submit">提交</button>
}

下拉框与多行文本(经典)

@{
  // 控制器中准备 SelectList
  // ViewBag.Categories = new SelectList(cats, "Id", "Name");
}

@Html.DropDownList("categoryId", (SelectList)ViewBag.Categories, "请选择")

@Html.TextArea("description", null, 4, 40, null)

@* 复选框与单选 *@
@Html.CheckBox("isActive", true)
@Html.RadioButton("level", "A") 优
@Html.RadioButton("level", "B") 良

标签帮助器(Core 推荐)

<!-- ASP.NET Core 标签帮助器,更贴近 HTML -->
<a asp-controller="Products" asp-action="Details"
   asp-route-id="@p.Id">详情</a>

<form asp-action="Create" asp-controller="Products" method="post">
  <label asp-for="Name"></label>
  <input asp-for="Name" />
  <span asp-validation-for="Name"></span>

  <select asp-for="CategoryId" asp-items="ViewBag.Categories"></select>

  <button type="submit">保存</button>
</form>
✅ 建议
新项目(ASP.NET Core)用标签帮助器;维护老项目时熟悉经典 Html.* 帮助器。两者本质相同:生成安全 HTML。

MVC 发布

把 MVC 应用发布部署到 Windows IIS 或 Linux 服务器(ASP.NET Core 跨平台)。

发布到文件夹

# 发布到 publish 目录
dotnet publish -c Release -o ./publish

部署到 IIS(Windows)

1. 服务器安装 IIS + .NET Hosting Bundle
2. 创建网站,物理路径指向 publish 目录
3. 应用池选择 "无托管代码"(Core) 或对应 .NET 版本
4. 授予 IIS_IUSRS 读取权限
5. Web.config 由 publish 自动生成(含 ASPNETCORE 配置)

部署到 Linux + Nginx

# 1. 服务器安装 .NET Runtime
# 2. publish 目录上传到 /var/www/myapp
# 3. 创建 systemd 服务托管
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My MVC App
After=network.target

[Service]
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/dotnet /var/www/myapp/MyApp.dll
Restart=always
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target
sudo systemctl enable myapp
sudo systemctl start myapp
# 4. Nginx 反向代理
server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

生产配置要点

  • 环境变量 ASPNETCORE_ENVIRONMENT=Production(关闭开发者异常页)
  • 启用 HTTPS(IIS 绑定证书 / Nginx 配置 SSL)
  • 连接字符串与密钥用环境变量,不入源码
  • 日志接入文件/集中日志系统
⚠️ 注意
经典 ASP.NET(.NET Framework)只能跑在 Windows/IIS;ASP.NET Core 才能跨平台部署到 Linux。生产环境务必启用 Production 环境变量。

MVC 参考手册

MVC 开发常用参考速查:路由、控制器、视图、验证、EF 常用 API 一览,方便查阅。

常用路由

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

// 特性路由
[Route("api/products")]
public class ApiController : Controller { }

[HttpGet("{id:int}")]   // 只匹配整数 id
public IActionResult Get(int id) { }

控制器常用

API说明
View(model)渲染视图并传模型
RedirectToAction("Index")跳转动作
Json(data)返回 JSON
NotFound()404
ModelState.IsValid模型验证结果
TempData["key"]跨请求临时数据

EF Core 常用

_db.Products.Find(id);            // 按主键查
_db.Products.Where(p => p.Price > 100);
_db.Products.OrderBy(p => p.Name);
_db.Products.FirstOrDefault(p => p.Id == id);
_db.Products.Any(p => p.Name == name);

// 新增/修改/删除
_db.Products.Add(p);   _db.SaveChanges();
_db.Products.Remove(p); _db.SaveChanges();
_db.Entry(p).State = EntityState.Modified;

// 原生 SQL(参数化)
_db.Database.ExecuteSqlRaw("DELETE FROM Products WHERE Id = {0}", id);

常用 NuGet 包

包用途
Microsoft.EntityFrameworkCoreORM 核心
Microsoft.EntityFrameworkCore.SqlServerSQL Server 提供程序
Pomelo.EntityFrameworkCore.MySqlMySQL 提供程序
Microsoft.AspNetCore.Authentication.JwtBearerJWT 认证
Serilog.AspNetCore日志
💡 提示
这份速查表覆盖了日常 80% 的 MVC 开发场景;遇到具体问题优先查 Microsoft Learn 官方文档。

WebForms 简介

ASP.NET Web Forms 是 ASP.NET 最早、最成熟的开发模式:基于控件 + 事件模型,拖拽控件、写事件处理器,像开发桌面程序一样开发网页。

核心概念

  • 页面文件:.aspx(HTML+控件)+ .aspx.cs(代码后置)
  • 服务端控件:Button、TextBox、GridView 等,在服务器渲染
  • 事件模型:Button 的 Click 事件在服务器端执行
  • 回发(PostBack):页面提交回自身处理事件
  • ViewState:自动保存页面状态

最小 Web Forms 页面

<%@ Page Language="C#" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="MySite.Default" %>

<!DOCTYPE html>
<html>
<body>
  <form id="form1" runat="server">
    <asp:Label ID="lblMessage" runat="server" Text="你好,Web Forms!" />
    <br />
    <asp:Button ID="btnClick" runat="server" Text="点我"
        />
  </form>
</body>
</html>

代码后置文件

public partial class Default : System.Web.UI.Page
{
    protected void btnClick_Click(object sender, EventArgs e)
    {
        lblMessage.Text = "你点击了按钮!当前时间:" + DateTime.Now;
    }
}
💡 提示
Web Forms 适合快速开发企业后台;缺点是 HTML 控制弱、ViewState 增加页面体积。微软已停止新特性开发,建议新项目用 MVC/Core。

WebForms 页面

Web Forms 页面由三部分组成:页面指令、HTML 标记、代码逻辑,支持拖拽式设计器快速布局。

页面指令(Page Directive)

<%@ Page Language="C#"
    AutoEventWireup="true"
    CodeBehind="Index.aspx.cs"
    Inherits="MySite.Index"
    Title="首页"
    Culture="zh-CN" %>

页面生命周期

Init        → 初始化控件
Load        → 加载数据(常用:绑定列表)
事件处理     → Button Click 等
PreRender   → 渲染前最后一次修改
Render      → 输出 HTML
Unload      → 清理资源

在 Page_Load 中加载数据

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)    // 首次加载才绑定,回发不重复
    {
        BindProducts();
    }
}

private void BindProducts()
{
    var list = GetProducts();
    gridProducts.DataSource = list;
    gridProducts.DataBind();
}

页面声明周期内常用事件

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    // 控件初始化
}

protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    // 页面加载
}

protected override void OnPreRender(EventArgs e)
{
    base.OnPreRender(e);
    // 渲染前
}
✅ 关键点
记住 IsPostBack:判断是首次加载还是回发。绑定数据的代码应放在 if (!IsPostBack) 内,避免回发时数据被覆盖。

WebForms 控件

Web Forms 的服务端控件是核心:在服务器端执行并渲染为 HTML。掌握常用控件能覆盖大多数界面需求。

常用标准控件

控件渲染用途
asp:Label<span>显示文本
asp:TextBox<input>文本输入
asp:Button<input type=button>提交按钮
asp:DropDownList<select>下拉框
asp:CheckBox<input type=checkbox>复选框
asp:RadioButtonListradio 组单选组
asp:GridView<table>数据表格

控件示例

<asp:TextBox ID="txtName" runat="server" Width="200" />
<br />

<asp:DropDownList ID="ddlCat" runat="server">
  <asp:ListItem Text="图书" Value="1" />
  <asp:ListItem Text="数码" Value="2" />
</asp:DropDownList>
<br />

<asp:CheckBox ID="chkAgree" runat="server" Text="同意条款" />
<br />

<asp:RadioButtonList ID="rblLevel" runat="server">
  <asp:ListItem Text="初级" Value="A" />
  <asp:ListItem Text="高级" Value="B" />
</asp:RadioButtonList>

代码中读写控件

// 读输入
string name = txtName.Text;
string catId = ddlCat.SelectedValue;
bool agree = chkAgree.Checked;

// 写输出
lblResult.Text = "你好," + name;
✅ 技巧
所有控件都带 runat="server" 属性才成为服务端控件;ID 用于代码中引用,命名用有意义的驼峰式。

WebForms 事件

Web Forms 的事件驱动模型:控件触发事件,服务器端处理。理解事件生命周期(何时触发、触发顺序)是调试的关键。

常用控件事件

<asp:Button ID="btnSave" runat="server" Text="保存"
    />

<asp:DropDownList ID="ddlCity" runat="server"
    AutoPostBack="true"            <!-- 选择即回发 -->
    />

<asp:TextBox ID="txtSearch" runat="server"
    AutoPostBack="true"
    />

事件处理代码

protected void btnSave_Click(object sender, EventArgs e)
{
    // 点击保存
    SaveData();
    lblMsg.Text = "保存成功";
}

protected void ddlCity_SelectedIndexChanged(object sender, EventArgs e)
{
    // 切换城市,加载对应区县
    BindDistricts(ddlCity.SelectedValue);
}

protected void txtSearch_TextChanged(object sender, EventArgs e)
{
    // 搜索框内容变化
    BindResults(txtSearch.Text);
}

事件触发顺序

1. Page.Init      (页面初始化)
2. 控件事件        (Button.Click 等)
3. Page.Load      (页面加载,通常在事件后执行)
4. Page.PreRender (渲染前)

注意:控件事件在 Load 之前触发,
因此 Load 中不要覆盖事件处理的成果

事件参数使用

// Command 事件可携带参数
<asp:LinkButton ID="lnkEdit" runat="server"
    CommandName="Edit" CommandArgument='<%# Eval("Id") %>'
    Text="编辑" />

protected void lnk_Command(object sender, CommandEventArgs e)
{
    if (e.CommandName == "Edit")
    {
        int id = Convert.ToInt32(e.CommandArgument);
        // 用 id 加载数据
    }
}
⚠️ 注意
需要回发的控件(DropDownList 等)要设 AutoPostBack="true";事件顺序与直觉相反(事件先于 Load),这是 Web Forms 最常见的坑。

WebForms 表单

Web Forms 的表单模型:一个页面通常只有一个 <form runat="server">,所有服务端控件都放其中,通过回发提交。

页面表单结构

<%@ Page Language="C#" AutoEventWireup="true"
    CodeBehind="Register.aspx.cs" Inherits="MySite.Register" %>

<html>
<body>
  <form id="form1" runat="server">
    <h2>用户注册</h2>

    <p>
      用户名:<asp:TextBox ID="txtName" runat="server" />
      <asp:RequiredFieldValidator ID="rfvName" runat="server"
          ControlToValidate="txtName" ErrorMessage="请输入用户名"
          ForeColor="Red" />
    </p>

    <p>
      密码:<asp:TextBox ID="txtPwd" runat="server" TextMode="Password" />
      <asp:RequiredFieldValidator ID="rfvPwd" runat="server"
          ControlToValidate="txtPwd" ErrorMessage="请输入密码"
          ForeColor="Red" />
    </p>

    <p>
      <asp:Button ID="btnReg" runat="server" Text="注册"
          />
    </p>

    <asp:Label ID="lblMsg" runat="server" ForeColor="Green" />
  </form>
</body>
</html>

表单处理代码

protected void btnReg_Click(object sender, EventArgs e)
{
    if (!Page.IsValid) return;   // 验证控件拦截后

    string name = txtName.Text;
    string pwd = txtPwd.Text;

    // 保存到数据库(参数化)
    using (var cmd = new SqlCommand(
        "INSERT INTO Users (Name, Pwd) VALUES (@n, @p)", conn))
    {
        cmd.Parameters.AddWithValue("@n", name);
        cmd.Parameters.AddWithValue("@p", pwd);
        cmd.ExecuteNonQuery();
    }

    lblMsg.Text = "注册成功!";
}

多按钮表单(指定 CommandName)

<asp:Button ID="btnSave" runat="server" Text="保存"
    CommandName="Save" />
<asp:Button ID="btnCancel" runat="server" Text="取消"
    CommandName="Cancel" />
protected void btn_Command(object sender, CommandEventArgs e)
{
    if (e.CommandName == "Save")
    {
        // 保存逻辑
    }
    else if (e.CommandName == "Cancel")
    {
        Response.Redirect("~/List.aspx");
    }
}
✅ 最佳实践
每个页面的服务端控件都放进唯一的 <form runat="server">;验证用验证控件,提交用按钮事件,逻辑集中清晰。

WebForms ViewState

ViewState 是 Web Forms 自动保存页面状态(控件值)的机制:回发后控件数据不丢失,但会增大页面体积。

ViewState 是什么

原理:
1. 页面首次渲染时,把控件状态编码成 __VIEWSTATE 隐藏字段
2. 回发时随表单提交回服务器
3. 服务器还原控件状态,开发者无需手动保存

代价:隐藏字段可能很大,拖慢页面

控制 ViewState

<!-- 页面级关闭 -->
<%@ Page EnableViewState="false" %>

<!-- 控件级关闭(大数据控件强烈建议) -->
<asp:GridView ID="grid" runat="server"
    EnableViewState="false" />
// 代码中设置
this.EnableViewState = false;
grid.EnableViewState = false;

何时必须保留 ViewState

场景建议
TextBox 输入内容保留(用户输入不能丢)
GridView 大数据关闭,在 Page_Load 重新绑定
动态控件保留或重新创建

ViewState 与安全

<!-- Web.config 开启 ViewState MAC 校验,防篡改 -->
<system.web>
  <pages viewStateEncryptionMode="Always"
        enableViewStateMac="true" />
</system.web>
✅ 优化
大列表控件(GridView)关闭 ViewState 并在 IsPostBack 时重新 DataBind,页面体积能减小 90% 以上,性能显著提升。

WebForms TextBox

TextBox 是最常用的输入控件,支持单行、多行、密码模式,配合验证控件使用。

基础用法

<!-- 单行文本 -->
<asp:TextBox ID="txtName" runat="server" Width="200"
    MaxLength="20" placeholder="请输入姓名" />

<!-- 密码框 -->
<asp:TextBox ID="txtPwd" runat="server" TextMode="Password" />

<!-- 多行文本 -->
<asp:TextBox ID="txtDesc" runat="server" TextMode="MultiLine"
    Rows="5" Columns="40" />

<!-- 数字框 -->
<asp:TextBox ID="txtPrice" runat="server"
    Text='<%# Eval("Price") %>' />

代码中读写

// 读值
string name = txtName.Text.Trim();

// 写值
txtDesc.Text = "这是默认内容";

// 设只读/禁用
txtName.ReadOnly = true;
txtName.Enabled = false;

AutoPostBack 场景

<asp:TextBox ID="txtSearch" runat="server"
    AutoPostBack="true"
    />
protected void txtSearch_TextChanged(object sender, EventArgs e)
{
    // 输入变化自动搜索
    BindResults(txtSearch.Text);
}

文本框校验组合

<asp:TextBox ID="txtEmail" runat="server" />
<asp:RequiredFieldValidator ID="rfvEmail" runat="server"
    ControlToValidate="txtEmail" ErrorMessage="邮箱必填"
    Display="Dynamic" ForeColor="Red" />
<asp:RegularExpressionValidator ID="revEmail" runat="server"
    ControlToValidate="txtEmail"
    ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"
    ErrorMessage="邮箱格式错误" Display="Dynamic" ForeColor="Red" />
✅ 技巧
多行文本框注意 Rows/Columns 指定尺寸;处理用户输入前先 .Trim(),输出前自动转义防 XSS。

WebForms Button

Button 控件触发服务器端事件,是 Web Forms 交互的核心。三种按钮控件满足不同场景。

三种按钮

<!-- Button:标准按钮 -->
<asp:Button ID="btnSave" runat="server" Text="保存"
    />

<!-- LinkButton:链接样式按钮 -->
<asp:LinkButton ID="lnkEdit" runat="server" Text="编辑"
    />

<!-- ImageButton:图片按钮 -->
<asp:ImageButton ID="imgSubmit" runat="server"
    ImageUrl="~/images/submit.png"
    />

事件处理

protected void btnSave_Click(object sender, EventArgs e)
{
    // 处理保存
    lblMsg.Text = "已保存";
}

protected void imgSubmit_Click(object sender, ImageClickEventArgs e)
{
    // e.X / e.Y 是点击位置
    lblMsg.Text = "点击位置: " + e.X + ", " + e.Y;
}

命令按钮与参数

<asp:Button ID="btnAction" runat="server"
    CommandName="Delete" CommandArgument='<%# Eval("Id") %>'
    Text="删除" />
protected void btnAction_Command(object sender, CommandEventArgs e)
{
    if (e.CommandName == "Delete")
    {
        int id = Convert.ToInt32(e.CommandArgument);
        DeleteRecord(id);
    }
}

按钮提交与验证

<!-- 触发页面验证 -->
<asp:Button ID="btnSubmit" runat="server" Text="提交"
    CausesValidation="true" />

<!-- 跳过验证(如"取消"按钮) -->
<asp:Button ID="btnCancel" runat="server" Text="取消"
    CausesValidation="false" />
✅ 最佳实践
页面上有多个按钮时,用 CommandName + OnCommand 统一处理;「取消/返回」按钮设 CausesValidation="false" 避免卡在验证上。

WebForms 数据绑定

Web Forms 的数据绑定把数据源与显示控件连接起来,支持多种绑定表达式与数据源控件。

绑定表达式

<!-- 绑定到数据源当前行字段 -->
<asp:Label ID="lblName" runat="server"
    Text='<%# Eval("Name") %>' />

<!-- 绑定到页面属性/方法 -->
<asp:Label ID="lblCount" runat="server"
    Text='<%# GetCount() %>' />

<!-- 格式化输出 -->
<asp:Label ID="lblPrice" runat="server"
    Text='<%# String.Format("¥{0:N2}", Eval("Price")) %>' />

DataBind() 触发

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // 触发页面上所有 <%# %> 表达式求值
        DataBind();
    }
}

Repeater 数据绑定

<asp:Repeater ID="repProducts" runat="server">
  <HeaderTemplate><ul></HeaderTemplate>
  <ItemTemplate>
    <li>
      <%# Eval("Name") %> -
      <%# Eval("Price", "¥{0:F2}") %>
    </li>
  </ItemTemplate>
  <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        repProducts.DataSource = GetProducts();
        repProducts.DataBind();
    }
}

GridView 数据绑定

<asp:GridView ID="gridProducts" runat="server"
    AutoGenerateColumns="false">
  <Columns>
    <asp:BoundField DataField="Id" HeaderText="ID" />
    <asp:BoundField DataField="Name" HeaderText="名称" />
    <asp:BoundField DataField="Price" HeaderText="价格"
        DataFormatString="¥{0:F2}" />
    <asp:TemplateField HeaderText="操作">
      <ItemTemplate>
        <asp:LinkButton ID="lnkEdit" runat="server" Text="编辑"
            CommandArgument='<%# Eval("Id") %>' />
      </ItemTemplate>
    </asp:TemplateField>
  </Columns>
</asp:GridView>
✅ 技巧
Eval() 用于单向显示,Bind() 用于可回写(如编辑模板);绑定表达式的值必须是页面上能访问到的公开属性/方法。

WebForms ArrayList

ArrayList 是 .NET 早期的动态数组集合,可以存放任意类型对象。虽已被 List<T> 取代,但旧项目中仍常见。

基本操作

using System.Collections;

// 创建
ArrayList list = new ArrayList();

// 添加
list.Add("苹果");
list.Add("香蕉");
list.Add(100);        // 可放任意类型
list.Add(DateTime.Now);

// 插入
list.Insert(1, "橘子");   // 插到索引 1

// 删除
list.Remove("苹果");      // 按值删
list.RemoveAt(0);         // 按下标删

// 访问
object first = list[0];
int count = list.Count;

遍历

foreach (object item in list)
{
    Response.Write(item.ToString() + "<br/>");
}

for (int i = 0; i < list.Count; i++)
{
    Response.Write(list[i] + "<br/>");
}

绑定到控件

ArrayList fruits = new ArrayList();
fruits.Add("苹果");
fruits.Add("香蕉");

ddlFruit.DataSource = fruits;
ddlFruit.DataBind();   // 每项显示为一项

repList.DataSource = fruits;
repList.DataBind();

与 List<T> 的对比

维度ArrayListList<T>
类型安全否(装 object)是(编译期检查)
性能装箱/拆箱开销无装箱
推荐度遗留代码新代码首选
✅ 建议
新代码一律用泛型 List<T>;只有维护旧代码时才接触 ArrayList。List<T> 用法几乎一致,但类型安全。

WebForms Hashtable

Hashtable 是 .NET 早期的键值对集合(键值对字典),用于快速按键查找。类似的概念在现代代码中是 Dictionary<K,V>。

基本操作

using System.Collections;

// 创建
Hashtable table = new Hashtable();

// 添加键值对
table["name"] = "张三";
table["age"] = 25;
table.Add("city", "北京");

// 读取
string name = (string)table["name"];
int age = (int)table["age"];

// 判断是否存在
bool hasKey = table.ContainsKey("city");
bool hasValue = table.ContainsValue("北京");

// 删除
table.Remove("city");
table.Clear();   // 清空

遍历

// 遍历所有键值
foreach (DictionaryEntry entry in table)
{
    Response.Write(entry.Key + " = " + entry.Value + "<br/>");
}

// 只遍历键
foreach (string key in table.Keys)
{
    Response.Write(key + "<br/>");
}

常用场景:配置项

// 模拟配置
Hashtable config = new Hashtable();
config["SiteName"] = "我的网站";
config["PageSize"] = "20";
config["EnableLog"] = "true";

string siteName = (string)config["SiteName"];
int pageSize = Convert.ToInt32(config["PageSize"]);
bool enableLog = Convert.ToBoolean(config["EnableLog"]);

与 Dictionary 对比

维度HashtableDictionary<K,V>
类型安全否是
性能需装箱更快
null 键不允许不允许(值可 null)
推荐度遗留代码新代码首选
✅ 建议
新代码用 Dictionary<string, int> 等泛型字典;Hashtable 只在维护旧代码时使用。

WebForms SortedList

SortedList 是按键自动排序的键值集合,遍历时按顺序输出,适合需要有序展示的键值数据。

基本操作

using System.Collections;

// 创建
SortedList list = new SortedList();

// 添加(自动按键排序)
list["banana"] = "香蕉";
list["apple"] = "苹果";
list["cherry"] = "樱桃";

// 读取
string v = (string)list["apple"];

// 按键顺序遍历(自动排序:apple→banana→cherry)
foreach (DictionaryEntry entry in list)
{
    Response.Write(entry.Key + " = " + entry.Value + "<br/>");
}

索引访问

// 按下标访问(排序后的位置)
object firstKey = list.GetKey(0);      // 第一个键
object firstVal = list.GetByIndex(0);  // 第一个值

// 找键的索引
int idx = list.IndexOfKey("apple");

// 删除
list.Remove("apple");
list.RemoveAt(0);

常见场景:排序展示

// 统计词频并按字母排序
SortedList wordCount = new SortedList();
wordCount["html"] = 12;
wordCount["css"] = 8;
wordCount["asp"] = 5;

foreach (DictionaryEntry entry in wordCount)
{
    Response.Write(entry.Key + ": " + entry.Value + "<br/>");
}
// 输出顺序: asp, css, html
✅ 建议
SortedList 适合数据量小且需要排序的场景;大数据量用 SortedDictionary<K,V> 性能更好。新代码优先泛型版本。

WebForms XML 文件

Web Forms 中读写 XML 文件:作为轻量数据存储、配置文件或数据交换格式。.NET 提供 XmlDocument、XDocument 等 API。

读取 XML

<?xml version="1.0" encoding="utf-8"?>
<books>
  <book id="1">
    <title>ASP.NET 入门</title>
    <price>59.9</price>
  </book>
  <book id="2">
    <title>C# 高级编程</title>
    <price>89.0</price>
  </book>
</books>
using System.Xml;

XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/App_Data/books.xml"));

// 遍历所有 book
XmlNodeList books = doc.SelectNodes("//book");
foreach (XmlNode book in books)
{
    string id = book.Attributes["id"].Value;
    string title = book.SelectSingleNode("title").InnerText;
    string price = book.SelectSingleNode("price").InnerText;
    Response.Write(id + " - " + title + " - " + price + "<br/>");
}

写入 XML

XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("~/App_Data/books.xml"));

XmlElement root = doc.DocumentElement;

// 新建 book 节点
XmlElement book = doc.CreateElement("book");
book.SetAttribute("id", "3");

XmlElement title = doc.CreateElement("title");
title.InnerText = "新书";
book.AppendChild(title);

XmlElement price = doc.CreateElement("price");
price.InnerText = "45.5";
book.AppendChild(price);

root.AppendChild(book);
doc.Save(Server.MapPath("~/App_Data/books.xml"));

绑定到控件

// 把 XML 作为数据源
DataSet ds = new DataSet();
ds.ReadXml(Server.MapPath("~/App_Data/books.xml"));

gridBooks.DataSource = ds.Tables[0];
gridBooks.DataBind();
✅ 建议
数据量小且结构固定用 XML 合适;数据量大、需查询建议用数据库。XDocument(LINQ to XML)语法更简洁,新代码推荐。

WebForms Repeater

Repeater 是最灵活的列表控件:它不自动生成任何 HTML,完全由你的模板决定,适合需要完全控制输出格式的场景。

模板结构

<asp:Repeater ID="repProducts" runat="server">
  <HeaderTemplate>
    <table><tr><th>名称</th><th>价格</th></tr>
  </HeaderTemplate>

  <ItemTemplate>
    <tr>
      <td><%# Eval("Name") %></td>
      <td><%# Eval("Price", "¥{0:F2}") %></td>
    </tr>
  </ItemTemplate>

  <AlternatingItemTemplate>
    <tr class="alt">
      <td><%# Eval("Name") %></td>
      <td><%# Eval("Price", "¥{0:F2}") %></td>
    </tr>
  </AlternatingItemTemplate>

  <FooterTemplate>
    </table>
  </FooterTemplate>
</asp:Repeater>

绑定数据

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        repProducts.DataSource = GetProducts();
        repProducts.DataBind();
    }
}

模板中的事件(ItemCommand)

<ItemTemplate>
  <tr>
    <td><%# Eval("Name") %></td>
    <td>
      <asp:LinkButton ID="lnkView" runat="server"
          CommandName="View" CommandArgument='<%# Eval("Id") %>'
          Text="查看" />
    </td>
  </tr>
</ItemTemplate>
protected void repProducts_ItemCommand(object source,
    RepeaterCommandEventArgs e)
{
    if (e.CommandName == "View")
    {
        int id = Convert.ToInt32(e.CommandArgument);
        Response.Redirect("~/Detail.aspx?id=" + id);
    }
}

分组/分隔符

<SeparatorTemplate>
  <hr />   <!-- 每两项之间插入 -->
</SeparatorTemplate>
✅ 适用
需要完全自定义输出(列表、卡片、导航菜单)时用 Repeater;需要内置分页/编辑等能力用 GridView/ListView。

WebForms DataList

DataList 用模板展示数据,支持多列布局与选择/编辑模式,介于 Repeater 与 GridView 之间。

多列布局

<asp:DataList ID="dlProducts" runat="server"
    RepeatColumns="3" RepeatDirection="Horizontal"
    RepeatLayout="Table">
  <ItemTemplate>
    <div class="product-card">
      <h3><%# Eval("Name") %></h3>
      <p>¥<%# Eval("Price", "{0:F2}") %></p>
      <asp:Button ID="btnBuy" runat="server" Text="加入购物车"
          CommandName="Buy" CommandArgument='<%# Eval("Id") %>' />
    </div>
  </ItemTemplate>
</asp:DataList>

选择模式

<asp:DataList ID="dlList" runat="server"
   >
  <ItemTemplate>
    <asp:LinkButton ID="lnkSelect" runat="server"
        CommandName="Select" Text='<%# Eval("Name") %>' />
  </ItemTemplate>

  <SelectedItemTemplate>
    <div style="background:#ffe;">
      已选中:<%# Eval("Name") %>
    </div>
  </SelectedItemTemplate>
</asp:DataList>

事件处理

protected void dlList_SelectedIndexChanged(object sender, EventArgs e)
{
    // 获取选中的数据项
    DataListItem item = dlList.SelectedItem;
    Label lbl = (Label)item.FindControl("lblName");
}

protected void dlProducts_ItemCommand(object source,
    DataListCommandEventArgs e)
{
    if (e.CommandName == "Buy")
    {
        int id = Convert.ToInt32(e.CommandArgument);
        AddToCart(id);
    }
}
✅ 选择建议
对比:Repeater(完全自由)、DataList(多列+选择)、GridView(表格+内置功能)。卡片/图库类展示优先 DataList。

WebForms 数据库连接

Web Forms 连接数据库的传统方式:ADO.NET(Connection、Command、DataReader)。虽然 EF 更现代,但理解 ADO.NET 仍有价值。

连接字符串(Web.config)

<connectionStrings>
  <add name="SiteDB"
       connectionString="Server=localhost;Database=knowledge_site;User Id=site;Password=***;"
       providerName="System.Data.SqlClient" />
</connectionStrings>

读取数据(DataReader)

using System.Data.SqlClient;

string connStr = ConfigurationManager
    .ConnectionStrings["SiteDB"].ConnectionString;

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    string sql = "SELECT Id, Name, Price FROM Products";
    using (SqlCommand cmd = new SqlCommand(sql, conn))
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        while (reader.Read())
        {
            Response.Write(reader["Name"] + " - " +
                reader["Price"] + "<br/>");
        }
    }
}

增删改(参数化)

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    string sql = "INSERT INTO Products (Name, Price) VALUES (@n, @p)";
    using (SqlCommand cmd = new SqlCommand(sql, conn))
    {
        cmd.Parameters.AddWithValue("@n", "新商品");
        cmd.Parameters.AddWithValue("@p", 99.9);
        cmd.ExecuteNonQuery();
    }
}

DataSet(离线数据)

using System.Data;
using System.Data.SqlClient;

DataSet ds = new DataSet();
using (SqlConnection conn = new SqlConnection(connStr))
{
    string sql = "SELECT * FROM Products";
    using (SqlDataAdapter adapter = new SqlDataAdapter(sql, conn))
    {
        adapter.Fill(ds, "Products");   // 填充到内存
    }
}

gridProducts.DataSource = ds.Tables["Products"];
gridProducts.DataBind();
⚠️ 注意
连接对象用 using 确保释放;SQL 语句必须参数化(@n),绝不能字符串拼接用户输入。

WebForms 母版页

母版页(Master Page)是 Web Forms 的"布局页":定义公共外观(页头、导航、页脚),内容页填充到占位符中。

母版页 Site.Master

<%@ Master Language="C#" %>

<!DOCTYPE html>
<html>
<head>
  <title><asp:ContentPlaceHolder ID="head" runat="server" /></title>
  <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
  <header>
    <h1>我的网站</h1>
    <nav>首页 | 关于 | 联系</nav>
  </header>

  <main>
    <asp:ContentPlaceHolder ID="MainContent" runat="server">
      默认内容(内容页不定义时显示)
    </asp:ContentPlaceHolder>
  </main>

  <footer>© 2026 我的网站</footer>
</body>
</html>

内容页引用母版页

<%@ Page Language="C#" MasterPageFile="~/Site.Master"
    Title="首页" %>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent"
    runat="server">
  <h2>欢迎访问</h2>
  <p>这是首页内容……</p>
</asp:Content>

多个母版页

Site.Master          → 前台页面(公开)
Admin.Master         → 后台页面(带管理导航)
Mobile.Master        → 移动端(简化布局)

// 内容页通过 MasterPageFile 属性选择
<%@ Page MasterPageFile="~/Admin.Master" %>

母版页访问

// 内容页中访问母版页控件
Master.FindControl("lblUser").Text = "管理员";

// 或定义母版页公开属性
public string SiteTitle { get; set; }   // 母版页代码后置

// 内容页:((SiteMaster)Master).SiteTitle = "新标题";
✅ 最佳实践
公共资源(CSS、导航、统计脚本)放母版页;页面差异内容放 ContentPlaceHolder。多个占位符可以分区块(如 head、MainContent、Scripts)。

WebForms 导航

Web Forms 提供 SiteMapPath、Menu、TreeView 三个导航控件,配合站点地图文件(Web.sitemap)自动生成导航。

站点地图 Web.sitemap

<?xml version="1.0" encoding="utf-8"?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0">
  <siteMapNode url="~/Default.aspx" title="首页">
    <siteMapNode url="~/Products/List.aspx" title="产品管理">
      <siteMapNode url="~/Products/Add.aspx" title="新增产品" />
    </siteMapNode>
    <siteMapNode url="~/About.aspx" title="关于我们" />
  </siteMapNode>
</siteMap>

Menu 导航菜单

<asp:Menu ID="menuNav" runat="server"
    DataSourceID="SiteMapDataSource1"
    Orientation="Horizontal"
    StaticDisplayLevels="1"
    MaximumDynamicDisplayLevels="2">
</asp:Menu>

<asp:SiteMapDataSource ID="SiteMapDataSource1"
    runat="server" />

面包屑 SiteMapPath

<asp:SiteMapPath ID="breadcrumb" runat="server"
    PathSeparator=" > " />
<!-- 输出: 首页 > 产品管理 > 新增产品 -->

TreeView 树形导航

<asp:TreeView ID="treeNav" runat="server"
    DataSourceID="SiteMapDataSource1"
    ShowLines="true" ExpandDepth="1">
</asp:TreeView>

代码动态导航

// 高亮当前菜单项
protected void Page_Load(object sender, EventArgs e)
{
    string path = Request.Url.AbsolutePath.ToLower();
    if (path.Contains("products"))
    {
        menuNav.SelectedValue = "~/Products/List.aspx";
    }
}
✅ 最佳实践
菜单用 SiteMapDataSource 自动从 Web.sitemap 生成,改地图即改导航;面包屑用 SiteMapPath 提升用户体验。

WebForms 实例

综合运用 Web Forms 知识,构建一个完整的商品管理系统:列表、新增、编辑、删除。

1. 母版页 Site.Master

<%@ Master Language="C#" %>
<html>
<body>
  <header><h1>📦 商品管理系统</h1></header>
  <nav>
    <a href="~/Default.aspx">商品列表</a> |
    <a href="~/Add.aspx">新增商品</a>
  </nav>
  <main>
    <asp:ContentPlaceHolder ID="MainContent" runat="server" />
  </main>
</body>
</html>

2. 列表页 Default.aspx

<%@ Page MasterPageFile="~/Site.Master" Title="商品列表" %>
<asp:Content ContentPlaceHolderID="MainContent" runat="server">
  <asp:GridView ID="grid" runat="server" AutoGenerateColumns="false"
     >
    <Columns>
      <asp:BoundField DataField="Id" HeaderText="ID" />
      <asp:BoundField DataField="Name" HeaderText="名称" />
      <asp:BoundField DataField="Price" HeaderText="价格"
          DataFormatString="¥{0:F2}" />
      <asp:TemplateField HeaderText="操作">
        <ItemTemplate>
          <asp:LinkButton ID="lnkEdit" runat="server" Text="编辑"
              CommandName="Edit" CommandArgument='<%# Eval("Id") %>' />
          <asp:LinkButton ID="lnkDel" runat="server" Text="删除"
              CommandName="Delete" CommandArgument='<%# Eval("Id") %>'
              />
        </ItemTemplate>
      </asp:TemplateField>
    </Columns>
  </asp:GridView>
</asp:Content>

3. 列表页代码后置

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack) BindGrid();
    }

    private void BindGrid()
    {
        // 查询并绑定(ADO.NET 参数化)
        grid.DataSource = GetProducts();
        grid.DataBind();
    }

    protected void grid_RowCommand(object sender,
        GridViewCommandEventArgs e)
    {
        int id = Convert.ToInt32(e.CommandArgument);
        if (e.CommandName == "Delete")
        {
            DeleteProduct(id);
            BindGrid();
        }
        else if (e.CommandName == "Edit")
        {
            Response.Redirect("~/Edit.aspx?id=" + id);
        }
    }
}

4. 新增/编辑页共用表单

<asp:Content ContentPlaceHolderID="MainContent" runat="server">
  <p>名称:<asp:TextBox ID="txtName" runat="server" /></p>
  <p>价格:<asp:TextBox ID="txtPrice" runat="server" /></p>
  <asp:Button ID="btnSave" runat="server" Text="保存"
      />
</asp:Content>
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack && Request["id"] != null)
    {
        // 编辑模式:回填数据
        int id = Convert.ToInt32(Request["id"]);
        var p = GetProduct(id);
        txtName.Text = p.Name;
        txtPrice.Text = p.Price.ToString();
    }
}

protected void btnSave_Click(object sender, EventArgs e)
{
    if (Request["id"] != null)
        UpdateProduct(Convert.ToInt32(Request["id"]));
    else
        InsertProduct();
    Response.Redirect("~/Default.aspx");
}
✅ 练习
为它加上:搜索框、分页(GridView 的 AllowPaging)、图片上传字段、登录权限。

WebForms HTML

Web Forms 页面本质是 HTML 与服务器控件的混合。理解服务器控件如何渲染为 HTML,才能写出结构良好的页面。

服务器控件 → HTML

控件渲染输出
asp:Label<span id="...">文本</span>
asp:TextBox<input type="text" name="...">
asp:Button<input type="submit" value="...">
asp:DropDownList<select>...</select>
asp:CheckBox<input type="checkbox">
asp:GridView<table>...</table>

HTML 控件(runat=server)

<!-- 普通 HTML 标签加 runat=server 后可在代码中操作 -->
<div id="box" runat="server" class="alert">提示信息</div>

<span id="msg" runat="server"></span>
// 操作 HTML 控件
box.Attributes["class"] = "alert-error";
box.InnerHtml = "<b>内容已更新</b>";
msg.InnerText = "纯文本(自动转义)";

客户端脚本集成

// 注册客户端脚本
protected void btn_Click(object sender, EventArgs e)
{
    ClientScript.RegisterStartupScript(this.GetType(), "alert",
        "alert('保存成功');", true);
}

// 控件添加客户端事件
btnSave.Attributes["onclick"] = "return confirm('确定提交?');";

在 HTML 中混用表达式

<p>当前用户:<%= Session["user"] %></p>
<p>页面路径:<%= Request.Url.AbsolutePath %></p>
✅ 技巧
需要简单样式/结构的元素用 runat="server" 的 HTML 标签;需要事件与状态的用服务器控件;静态内容保持纯 HTML。

WebForms Controls

深入了解 Web Forms 服务器控件体系:标准控件、数据控件、验证控件、登录控件,按需选用。

控件分类

分类代表控件
标准控件Label、TextBox、Button、DropDownList
数据控件GridView、Repeater、DataList、ListView、DetailsView
验证控件RequiredFieldValidator、RangeValidator 等
导航控件Menu、TreeView、SiteMapPath
登录控件Login、CreateUserWizard、ChangePassword
AJAX 控件UpdatePanel、Timer

DetailsView(单条数据)

<asp:DetailsView ID="dvProduct" runat="server"
    AutoGenerateRows="false" DataKeyNames="Id">
  <Fields>
    <asp:BoundField DataField="Name" HeaderText="名称" />
    <asp:BoundField DataField="Price" HeaderText="价格" />
  </Fields>
</asp:DetailsView>

ListView(灵活列表+分页)

<asp:ListView ID="lvProducts" runat="server"
    DataKeyNames="Id">
  <LayoutTemplate>
    <ul id="itemPlaceholderContainer" runat="server">
      <li id="itemPlaceholder" runat="server"></li>
    </ul>
  </LayoutTemplate>
  <ItemTemplate>
    <li><%# Eval("Name") %> - ¥<%# Eval("Price") %></li>
  </ItemTemplate>
</asp:ListView>

登录控件(快速实现)

<asp:Login ID="loginCtrl" runat="server"
   >
</asp:Login>
protected void loginCtrl_Authenticate(object sender,
    AuthenticateEventArgs e)
{
    if (CheckUser(loginCtrl.UserName, loginCtrl.Password))
    {
        e.Authenticated = true;
        FormsAuthentication.SetAuthCookie(loginCtrl.UserName, false);
    }
}
✅ 建议
快速原型用登录/数据控件(Login、DetailsView);正式项目建议 MVC/Core + 手写界面,控制力更强。

WebForms Validation

验证控件在客户端与服务器端双重校验用户输入,是 Web Forms 表单的标配。

常用验证控件

控件用途
RequiredFieldValidator必填
RangeValidator范围(数字/日期/字符串)
CompareValidator比较两个控件/与固定值
RegularExpressionValidator正则格式
CustomValidator自定义校验函数
ValidationSummary汇总显示所有错误

完整示例

<asp:TextBox ID="txtAge" runat="server" />
<asp:RequiredFieldValidator ID="rfvAge" runat="server"
    ControlToValidate="txtAge" ErrorMessage="年龄必填"
    Display="Dynamic" ForeColor="Red" />
<asp:RangeValidator ID="rvAge" runat="server"
    ControlToValidate="txtAge" Type="Integer"
    MinimumValue="1" MaximumValue="150"
    ErrorMessage="年龄在 1~150 之间"
    Display="Dynamic" ForeColor="Red" />
<br />

<asp:TextBox ID="txtPwd2" runat="server" TextMode="Password" />
<asp:CompareValidator ID="cvPwd" runat="server"
    ControlToValidate="txtPwd2" ControlToCompare="txtPwd"
    ErrorMessage="两次密码不一致" ForeColor="Red" />
<br />

<asp:CustomValidator ID="cstvName" runat="server"
    ControlToValidate="txtName"
   
    ErrorMessage="用户名已存在" ForeColor="Red" />
<br />

<asp:ValidationSummary ID="vs" runat="server"
    HeaderText="请修正以下错误:" ForeColor="Red" />

<asp:Button ID="btnSubmit" runat="server" Text="提交"
    />

自定义校验代码

protected void cstvName_ServerValidate(object source,
    ServerValidateEventArgs args)
{
    // 判断用户名是否已存在
    args.IsValid = !CheckExists(args.Value);
}

protected void btnSubmit_Click(object sender, EventArgs e)
{
    if (!Page.IsValid) return;   // 全部验证通过才处理
    // 保存逻辑……
}

分组验证

<!-- 不同区域分开验证 -->
<asp:TextBox ID="txt1" runat="server" />
<asp:RequiredFieldValidator ID="rfv1" runat="server"
    ControlToValidate="txt1" ErrorMessage="必填"
    ValidationGroup="Step1" />

<asp:Button ID="btnNext" runat="server" Text="下一步"
    ValidationGroup="Step1" />
✅ 最佳实践
每个验证控件设 Display="Dynamic" 避免留白抖动;提交时先检查 Page.IsValid;多步骤表单用 ValidationGroup 分组。

ASP.NET

回顾 ASP.NET 整体知识体系,串起 Web Pages / MVC / Web Forms 三条路线,给出选型建议与学习路径。

ASP.NET 三大路线总结

路线核心适合现状
Web PagesRazor 单页小型网站/学习成熟稳定
MVC分层架构专业团队/中大型主流推荐
Web Forms控件事件企业快速开发停止新特性

技术选型建议

  • 新项目:ASP.NET Core MVC(跨平台、高性能、生态好)
  • 维护老项目:按原技术栈(Web Forms/MVC)继续
  • 学习入门:先 Razor 语法,再 MVC 架构

ASP.NET Core 新特性

特性说明
跨平台Windows/Linux/macOS 运行
性能TechEmpower 榜单常年领先
依赖注入内置 IoC 容器
模块化中间件请求管道可自由组装
统一编程模型MVC + Web API + Razor Pages 合一

推荐学习路径

C# 基础 → Razor 语法 → MVC 架构
    → EF Core 数据访问 → 身份认证 → 部署发布
💡 提示
ASP.NET 至今仍是企业级 Web 开发的主力框架之一。掌握 C# + MVC + EF Core 这套组合,就业与实战都够用。

MVC 教程

本教程是 ASP.NET MVC 专题合集:从创建项目到发布部署的完整流程,附带一个可运行的示例(学生管理)。

1. 创建项目与模型

dotnet new mvc -n StudentApp
public class Student
{
    public int Id { get; set; }
    [Required] public string Name { get; set; }
    public int Score { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<Student> Students { get; set; }
    public AppDbContext(DbContextOptions<AppDbContext> o) : base(o) { }
}

2. 控制器

public class StudentsController : Controller
{
    private readonly AppDbContext _db;
    public StudentsController(AppDbContext db) { _db = db; }

    public IActionResult Index()
    {
        return View(_db.Students.OrderByDescending(s => s.Score).ToList());
    }

    [HttpGet]
    public IActionResult Create() { return View(); }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Create(Student s)
    {
        if (ModelState.IsValid)
        {
            _db.Students.Add(s);
            _db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(s);
    }
}

3. 视图

@* Views/Students/Index.cshtml *@
@model IEnumerable<Student>

<h2>学生列表</h2>
<table>
  <tr><th>姓名</th><th>成绩</th></tr>
@foreach (var s in Model)
{
  <tr><td>@s.Name</td><td>@s.Score</td></tr>
}
</table>
<a asp-action="Create">新增学生</a>

4. 运行

dotnet ef migrations add Init
dotnet ef database update
dotnet run
# 访问 http://localhost:5000/Students
✅ 练习
给这个示例加上:详情页、编辑、删除、按成绩搜索、分页。完成后你就掌握了 MVC 的核心闭环。

Razor 教程

本教程是 Razor 语法专题合集:总结最常用的 Razor 模式,附可直接运行的代码片段,适合随时查阅。

速查表

@* 输出 *@
@variable
@(expression)

@* 代码块 *@
@{ var x = 1; }

@* 条件 *@
@if (x > 0) { <p>正数</p> }
@switch (x) { case 1: <p>一</p>; break; }

@* 循环 *@
@for (var i = 0; i < 10; i++) { }
@foreach (var item in list) { }
@while (n > 0) { }

@* 注释 *@
@* 服务端注释 *@
<!-- 客户端注释 -->

@* 输出 @ 符号 *@
@@example.com

布局与视图

@* 布局 *@
@{
    Layout = "_Layout";
    ViewBag.Title = "页面标题";
}

@* 区域 *@
@section Scripts { <script>...</script> }

@* 分部视图 *@
@await Html.PartialAsync("_ProductCard", model)

模型与数据

@* 声明模型 *@
@model List<Product>

@* 遍历 *@
@foreach (var p in Model) { <p>@p.Name</p> }

@* 空判断 *@
@if (Model != null && Model.Any()) { ... }

@* 显示模型属性(防 XSS) *@
@Html.DisplayFor(m => m.Name)

HTML/标签帮助器

<!-- 链接 -->
<a asp-controller="Home" asp-action="About">关于</a>

<!-- 表单 -->
<form asp-action="Create" method="post">
  <input asp-for="Name" />
  <span asp-validation-for="Name"></span>
  <button type="submit">保存</button>
</form>
✅ 提示
把这一页当作你的 Razor 快速手册:写视图时对照速查,遇到不熟的语法回来看一眼即可。

Web Forms 教程

本教程是 Web Forms 专题合集:回顾控件、事件、状态、数据访问的核心用法,汇总成可查阅的速查手册。

常用控件速查

<asp:Label ID="lbl" runat="server" Text="文本" />
<asp:TextBox ID="txt" runat="server" TextMode="SingleLine" />
<asp:Button ID="btn" runat="server" Text="提交" />
<asp:DropDownList ID="ddl" runat="server" AutoPostBack="true">
  <asp:ListItem Text="选项1" Value="1" />
</asp:DropDownList>
<asp:CheckBox ID="chk" runat="server" Text="同意" />
<asp:RadioButtonList ID="rbl" runat="server">
  <asp:ListItem Text="男" Value="M" />
  <asp:ListItem Text="女" Value="F" />
</asp:RadioButtonList>

页面生命周期速记

Init → 控件事件 → Load → 回发处理 → PreRender → Render → Unload

if (!IsPostBack) { 首次加载逻辑 }

状态管理

机制范围说明
ViewState页面自动保存控件状态,会增大体积
Session用户会话登录态等用户级数据
Cookie浏览器持久化小数据
Application全站全局共享数据(如统计)

数据访问三步

// 1. 连接
using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    // 2. 命令(参数化!)
    using (SqlCommand cmd = new SqlCommand(
        "SELECT * FROM Products WHERE Price > @p", conn))
    {
        cmd.Parameters.AddWithValue("@p", 100);
        // 3. 执行
        using (SqlDataReader r = cmd.ExecuteReader())
        {
            while (r.Read()) { /* 读数据 */ }
        }
    }
}

Web Forms 生存指南

  • 旧系统迁移:优先用 EF 替换手写 ADO.NET
  • 性能:大数据控件关闭 ViewState
  • 安全:参数化 SQL + 验证控件 + 防 CSRF
  • 新功能用 Core 增量开发,逐步替换
✅ 总结
Web Forms 虽然不再有新特性,但存量系统庞大。掌握其核心模式(控件+事件+状态),维护与迁移都不慌。

Web Pages 教程

本教程是 Web Pages 专题合集:把 Razor、布局、数据库、安全等常用模式汇总成速查手册。

页面基础

@{
  Layout = "~/_Layout.cshtml";      // 布局
  Page.Title = "标题";              // 页面变量
  var db = Database.Open("SiteDB"); // 数据库
}

@* 输出 *@
<p>@Page.Title</p>

数据库操作

@{
  // 查询
  var rows = db.Query("SELECT * FROM Products");
  var row = db.QuerySingle("SELECT * FROM Products WHERE Id=@0", 1);
  var cnt = db.QueryValue("SELECT COUNT(*) FROM Products");

  // 增删改(参数化)
  db.Execute("INSERT INTO Products (Name, Price) VALUES (@0,@1)", "A", 9.9);
  db.Execute("UPDATE Products SET Price=@0 WHERE Id=@1", 19.9, 1);
  db.Execute("DELETE FROM Products WHERE Id=@0", 1);
}

表单处理

@{
  if (IsPost)
  {
    var name = Request.Form["username"];
    var id = Request.QueryString["id"];
    // 校验与处理
  }
}

<form method="post">
  <input name="username">
  <button type="submit">提交</button>
</form>

安全(WebSecurity)

@{
  // 注册/登录
  WebSecurity.CreateUserAndAccount(email, pwd);
  WebSecurity.Login(email, pwd, persistCookie: true);
  WebSecurity.Logout();

  // 判断
  if (WebSecurity.IsAuthenticated) { }
  var user = WebSecurity.CurrentUserName;
}

布局与帮助器

@* 渲染公共片段 *@
@RenderPage("~/Shared/_Header.cshtml")

@* 自定义帮助器(在 App_Code) *@
@MyHelpers.ShowAlert("消息")

@* 数据网格 *@
@{
  var grid = new WebGrid(source: db.Query("SELECT * FROM Products"));
}
@grid.GetHtml(rowsPerPage: 10)
✅ 总结
Web Pages 适合快速原型与小站。核心就三件事:Razor 输出、Database 增删改查、WebSecurity 登录。掌握即可独立开发。
第 1 / 67 节