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 简介
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 Pages | MVC | Web Forms |
|---|---|---|---|
| 页面模型 | 单页脚本 | 控制器+视图 | 控件+事件 |
| 学习曲线 | 低 | 中 | 中 |
| 控制 HTML | 完全 | 完全 | 部分(控件生成) |
| 适合团队 | 个人/小站 | 专业团队 | 快速交付 |
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>
@ 切换;代码块内多行语句用 @{ ... } 包裹,单表达式直接 @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 的辅助类) |
/Content | CSS、图片等静态资源 |
/Scripts | JavaScript 文件 |
/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)
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)
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
);
}
Web.config 或环境变量中;生产环境建议使用企业邮件服务(如腾讯云、阿里云 SES)。WebPages PHP
Web Pages 支持在页面中使用 PHP 辅助功能——事实上本教程这一节介绍的是 Web Pages 中与 PHP 类似的概念对照,帮助有 PHP 背景的开发者快速迁移。
概念对照
| PHP | ASP.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")
}
$_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"
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(); }
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())
}
Razor 简介
Razor 是 ASP.NET 的标记语法(视图引擎),让你在 HTML 中直接用 C#/VB 编写服务端逻辑。它是 MVC 与 Web Pages 的共用基础。
为什么用 Razor
- 简洁:
@一个字符切换代码模式 - 智能提示:Visual Studio 中代码高亮与补全
- HTML 友好:与 HTML 标签自然混写
- 自动转义:默认转义 HTML,防 XSS
Razor 文件类型
| 文件 | 引擎 | 用途 |
|---|---|---|
.cshtml | C# Razor | MVC 视图、Web Pages |
.vbhtml | VB Razor | VB 版本视图 |
最小 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 语法
掌握 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;
}
@ 后的代码与 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>
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
&、代码块用 @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
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
| 维度 | MVC | Web Forms |
|---|---|---|
| URL | 语义化 /Product/1 | 页面文件 .aspx |
| HTML 控制 | 完全控制 | 控件生成 |
| 状态管理 | 无 ViewState | ViewState 自动 |
| 可测试性 | 高(依赖注入) | 低 |
第一个控制器
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "欢迎使用 MVC";
return View();
}
}
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 | 布局、分部视图、错误页 |
/wwwroot | CSS/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/
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
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
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>
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)
- 连接字符串与密钥用环境变量,不入源码
- 日志接入文件/集中日志系统
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.EntityFrameworkCore | ORM 核心 |
Microsoft.EntityFrameworkCore.SqlServer | SQL Server 提供程序 |
Pomelo.EntityFrameworkCore.MySql | MySQL 提供程序 |
Microsoft.AspNetCore.Authentication.JwtBearer | JWT 认证 |
Serilog.AspNetCore | 日志 |
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;
}
}
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:RadioButtonList | radio 组 | 单选组 |
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 加载数据
}
}
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>
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> 的对比
| 维度 | ArrayList | List<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 对比
| 维度 | Hashtable | Dictionary<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
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();
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>
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);
}
}
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 = "新标题";
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");
}
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);
}
}
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 Pages | Razor 单页 | 小型网站/学习 | 成熟稳定 |
| 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 数据访问 → 身份认证 → 部署发布
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
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>
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 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)