一、CodeBehind 架構與頁面生命週期
- .aspx:宣告式標記 (控制項樹、HTML、CSS)、`Inherits="Namespace.Class"` 指向後端類別。
- .aspx.cs:`partial class PageName : System.Web.UI.Page`,`protected` 欄位自動對應 ``。
- **存取層級**:後端類別為頁面類別的**子類別** (透過動態編譯繼承),因此 `.aspx` 內 `<% %>` 程式碼塊與控制項事件處理函式,**必須是 `protected` 或 `public`**,`private` 會導致編譯錯誤 (CS0122)。
二、常用伺服器控制項進階用法
Literal — XSS 防護關鍵
<asp:Literal ID="litContent" runat="server" Mode="Encode" />
TextBox — AutoPostBack 即時驗證
<asp:TextBox ID="txtEmail" runat="server" AutoPostBack="true" OnTextChanged="txtEmail_TextChanged" />
Button — OnClientClick 客戶端確認
<asp:Button ID="btnDelete" runat="server" Text="刪除"
OnClientClick="return confirm('確定刪除?');"
OnClick="btnDelete_Click" />
CommandName/CommandArgument — 多按鈕共用事件
<asp:Button ID="btnEdit" runat="server" CommandName="Edit" CommandArgument='<%# Eval("ID") %>' OnCommand="btn_Command" />
<asp:Button ID="btnDel" runat="server" CommandName="Delete" CommandArgument='<%# Eval("ID") %>' OnCommand="btn_Command" />
protected void btn_Command(object sender, CommandEventArgs e)
{
int id = Convert.ToInt32(e.CommandArgument);
if (e.CommandName == "Edit") Response.Redirect($"Edit.aspx?id={id}");
else if (e.CommandName == "Delete") DeleteRecord(id);
}
Panel — GroupingText 渲染為 Fieldset
<asp:Panel ID="pnlAdvanced" runat="server" GroupingText="進階設定" DefaultButton="btnSearch">
<!-- 內容 -->
</asp:Panel>
HyperLink — 虛擬路徑自動解析
<asp:HyperLink ID="hlHome" runat="server" NavigateUrl="~/Default.aspx" Text="首頁" />
FileUpload — 完整上傳流程與安全
protected void btnUpload_Click(object sender, EventArgs e)
{
if (!fuPhoto.HasFile) { ShowMsg("請選擇檔案"); return; }
// 1. 副檔名白名單
string ext = System.IO.Path.GetExtension(fuPhoto.FileName).ToLower();
string[] allow = { ".jpg", ".jpeg", ".png", ".gif", ".pdf" };
if (!allow.Contains(ext)) { ShowMsg("副檔名不允許"); return; }
// 2. 檔案大小限制 (web.config httpRuntime maxRequestLength 亦需設定)
if (fuPhoto.PostedFile.ContentLength > 2 * 1024 * 1024) { ShowMsg("超過 2MB"); return; }
// 3. 安全儲存:隨機檔名 + 非網頁執行目錄
string saveDir = Server.MapPath("~/App_Data/Uploads/");
if (!System.IO.Directory.Exists(saveDir)) System.IO.Directory.CreateDirectory(saveDir);
string saveName = Guid.NewGuid().ToString("N") + ext;
fuPhoto.SaveAs(System.IO.Path.Combine(saveDir, saveName));
// 4. 記錄資料庫...
ShowMsg("上傳成功");
}
⚠️ **漏洞防護**:上傳目錄**禁用執行權限** (IIS 處理常式對應移除 .aspx/.ashx/.php),檔名**隨機化**,副檔名**白名單**,儲存於 **App_Data 或網站根目錄外**。
三、驗證控制項完整體系
| 控制項 | 用途 | 關鍵屬性 |
| RequiredFieldValidator | 必填 | `ControlToValidate`, `InitialValue` (下拉預設值), `ErrorMessage`/`Text` |
| RangeValidator | 範圍 (數字/日期/字串) | `Type`, `MinimumValue`, `MaximumValue` |
| CompareValidator | 比較 (兩欄位相等/大小/型別) | `ControlToCompare`, `Operator`, `Type` |
| RegularExpressionValidator | 正則驗證 (Email/電話/身分證) | `ValidationExpression` |
| CustomValidator | 自訂邏輯 | `OnServerValidate` (後端), `ClientValidationFunction` (前端 JS) |
| ValidationSummary | 錯誤摘要顯示 | `ShowMessageBox`, `ShowSummary`, `HeaderText` |
共同核心機制
- `ValidationGroup`:分組驗證,按鈕同組才觸發該組驗證 (單頁多表單)。
- `CausesValidation`:控制項是否觸發驗證 (預設 `true`,取消按鈕設 `false`)。
- **伺服器端必檢**:`if (!Page.IsValid) return;` — **防止繞過客戶端驗證** (JS 停用/攔截)。
- `Text` vs `ErrorMessage`:`Text` 顯示於驗證控制項位置 (通常 `*`),`ErrorMessage` 顯示於 `ValidationSummary`。
CustomValidator 範例:伺服器端檢查帳號唯一
<asp:CustomValidator ID="cvUser" runat="server" ControlToValidate="txtUser"
OnServerValidate="cvUser_ServerValidate" ErrorMessage="帳號已存在" />
protected void cvUser_ServerValidate(object source, ServerValidateEventArgs args)
{
args.IsValid = !UserBLL.Exists(args.Value); // false = 驗證失敗
}
四、母版頁面 — 版面共用與穿透式存取
基本結構
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WebApp.SiteMaster" %>
<html><body>
<header><asp:ContentPlaceHolder ID="head" runat="server" /></header>
<nav><asp:LinkButton ID="lnkLogout" runat="server" OnClick="lnkLogout_Click" /></nav>
<main><asp:ContentPlaceHolder ID="MainContent" runat="server" /></main>
<footer>© 2024</footer>
</body></html>
內容頁面
<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeBehind="Default.aspx.cs" Inherits="WebApp.Default" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<h1>首頁內容</h1>
</asp:Content>
內容頁存取母版控制項/屬性/方法
// 1. FindControl (弱型別)
Button btn = (Button)Master.FindControl("lnkLogout");
if (btn != null) btn.Visible = false;
// 2. 強型別 (需 @MasterType 指令)
<%@ MasterType VirtualPath="~/Site.master" %>
// 之後可直接 this.Master.LnkLogout.Visible = false;
// 前提:母版頁面公開 public 屬性/方法
public LinkButton LnkLogout => lnkLogout;
public void SetNavActive(string navId) { /* 邏輯 */ }
母版頁面存取內容頁控制項 (較少見)
ContentPlaceHolder cp = (ContentPlaceHolder)FindControl("MainContent");
if (cp != null)
{
TextBox txt = (TextBox)cp.FindControl("txtSearch");
// ...
}
五、虛擬路徑解析:ResolveUrl vs ResolveClientUrl
| 方法 | 行為 | 適用場景 |
Page.ResolveUrl("~/Images/x.jpg") | 回傳 **根相對路徑** `/AppName/Images/x.jpg` | 伺服器端控制項屬性、CSS `url()`、一般情況 |
Page.ResolveClientUrl("~/Images/x.jpg") | 回傳 **相對目前頁面** 的路徑 `../Images/x.jpg` 或 `Images/x.jpg` | 客戶端 ` `、``、JS 動態產生 URL |
VirtualPathUtility.ToAbsolute("~/Images/x.jpg") | 同 `ResolveUrl`,無需 Page 實例 | 非頁面類別 (Helper、BLL、Handler) |
母版頁面中路徑解析陷阱
- 母版與內容頁**目錄層級不同**時,`
` 在母版 `` 中**不會**自動解析 (非伺服器控制項)。
- 解法:`
` 或改用 ``。
六、註冊頁面完整範例 (Register.aspx/.cs) — 整合驗證、jQuery 客戶端強度檢查、PRG 模式
<%@ Page Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" CodeBehind="Register.aspx.cs" Inherits="WebApp.Register" %>
<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
<asp:ValidationSummary ID="vsMain" runat="server" CssClass="alert alert-danger" HeaderText="請修正下列錯誤:" />
<table class="form-table">
<tr>
<td><asp:Label Text="帳號" AssociatedControlID="txtUser" /></td>
<td>
<asp:TextBox ID="txtUser" runat="server" AutoPostBack="true" OnTextChanged="txtUser_TextChanged" />
<asp:RequiredFieldValidator ControlToValidate="txtUser" Text="*" ErrorMessage="帳號必填" />
<asp:CustomValidator ID="cvUser" ControlToValidate="txtUser" OnServerValidate="cvUser_ServerValidate" Text="*" ErrorMessage="帳號已存在" />
<asp:Label ID="lblUserMsg" runat="server" CssClass="text-danger" Visible="false" />
</td>
</tr>
<tr>
<td><asp:Label Text="密碼" AssociatedControlID="txtPwd" /></td>
<td>
<asp:TextBox ID="txtPwd" runat="server" TextMode="Password" />
<asp:RequiredFieldValidator ControlToValidate="txtPwd" Text="*" ErrorMessage="密碼必填" />
<span id="pwdStrength"></span> <!-- jQuery 即時強度 -->
</td>
</tr>
<tr>
<td><asp:Label Text="確認密碼" AssociatedControlID="txtPwd2" /></td>
<td>
<asp:TextBox ID="txtPwd2" runat="server" TextMode="Password" />
<asp:CompareValidator ControlToValidate="txtPwd2" ControlToCompare="txtPwd" Operator="Equal" Text="*" ErrorMessage="兩次密碼不一致" />
</td>
</tr>
<tr>
<td><asp:Label Text="Email" AssociatedControlID="txtEmail" /></td>
<td>
<asp:TextBox ID="txtEmail" runat="server" />
<asp:RegularExpressionValidator ControlToValidate="txtEmail" ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" Text="*" ErrorMessage="Email 格式錯誤" />
</td>
</tr>
<tr>
<td></td><td><asp:Button ID="btnReg" Text="註冊" OnClick="btnReg_Click" /></td>
</tr>
</table>
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="head" runat="server">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
$(function(){
$('#<%=txtPwd.ClientID%>').on('blur', function(){
var v = $(this).val();
if (v.length < 6) $('#pwdStrength').text('弱').css('color','red');
else if (/[a-z]/.test(v) && /[A-Z]/.test(v) && /[0-9]/.test(v)) $('#pwdStrength').text('強').css('color','green');
else $('#pwdStrength').text('中').css('color','orange');
});
});
</script>
</asp:Content>
protected void btnReg_Click(object sender, EventArgs e)
{
if (!Page.IsValid) return; // 關鍵:伺服器端二次驗證
// 密碼雜湊
string hash = BCrypt.Net.BCrypt.HashPassword(txtPwd.Text); // NuGet: BCrypt.Net-Next
long userId = UserBLL.Insert(txtUser.Text.Trim(), hash, txtEmail.Text.Trim());
Session["UserId"] = userId;
Session["Auth"] = true;
// PRG 模式:Redirect 避免 F5 重複提交
Response.Redirect("Target.aspx?msg=註冊成功&url=Default.aspx");
}
七、三種控制項類型比較
總結
- **CodeBehind**:`protected` 欄位對應控制項,母版頁面 `@MasterType` 強型別存取。
- **驗證控制項**:`ValidationGroup` 分組、`Page.IsValid` 伺服器端必檢、`CustomValidator` 彈性擴充。
- **FileUpload**:白名單、大小限制、隨機檔名、非執行目錄、資料庫記錄。
- **母版頁面**:版面共用、`ContentPlaceHolder` 注入、`FindControl`/`@MasterType` 雙向存取。
- **路徑解析**:`ResolveUrl` (根相對) vs `ResolveClientUrl` (相對目前頁),母版頁面非伺服器標籤必須手動解析。
- **現代化建議**:新專案考慮 **ASP.NET Core Razor Pages / Blazor**,標記與邏輯更分離、支援 Dependency Injection、Tag Helpers 取代 Server Controls、原生支援現代前端工具鏈。