简体中文 繁體中文 English 日本語 Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français

站内搜索

搜索

活动公告

11-02 12:46
10-23 09:32
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,将及时处理!
10-23 09:31
10-23 09:28
通知:签到时间调整为每日4:00(东八区)
10-23 09:26

探索ASP.NET Web Forms模板的强大功能与实际应用技巧助您快速构建高效稳定的Web应用程序解决开发中的常见挑战

3万

主题

423

科技点

3万

积分

大区版主

木柜子打湿

积分
31916

三倍冰淇淋无人之境【一阶】财Doro小樱(小丑装)立华奏以外的星空【二阶】⑨的冰沙

发表于 2025-9-29 11:10:00 | 显示全部楼层 |阅读模式 [标记阅至此楼]

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

ASP.NET Web Forms作为微软.NET框架中的重要组成部分,自2002年首次发布以来,一直是构建企业级Web应用程序的重要工具。尽管近年来ASP.NET MVC和ASP.NET Core等新技术获得了更多关注,但Web Forms凭借其成熟稳定、开发效率高和易于上手的特点,仍在许多企业和开发团队中广泛使用。本文将深入探索ASP.NET Web Forms模板的强大功能,分享实际应用技巧,并帮助开发者解决在构建高效稳定的Web应用程序过程中遇到的常见挑战。

ASP.NET Web Forms模板的核心功能

1. 事件驱动编程模型

ASP.NET Web Forms采用了类似Windows Forms的事件驱动编程模型,使开发者能够以更直观的方式构建Web应用程序。这种模型将复杂的HTTP请求/响应机制抽象为服务器控件和事件,大大简化了Web开发过程。
  1. // 示例:按钮点击事件处理
  2. protected void btnSubmit_Click(object sender, EventArgs e)
  3. {
  4.     // 处理表单提交逻辑
  5.     string name = txtName.Text;
  6.     string email = txtEmail.Text;
  7.    
  8.     // 保存到数据库
  9.     SaveUserData(name, email);
  10.    
  11.     // 显示成功消息
  12.     lblMessage.Text = "数据保存成功!";
  13.     lblMessage.ForeColor = System.Drawing.Color.Green;
  14. }
复制代码

2. 丰富的服务器控件

Web Forms提供了大量内置服务器控件,如TextBox、Button、GridView、Repeater等,这些控件封装了HTML生成和状态管理的复杂性,使开发者能够专注于业务逻辑。
  1. <%-- 示例:GridView控件绑定数据 --%>
  2. <asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="False"
  3.     OnRowCommand="gvProducts_RowCommand" DataKeyNames="ProductID">
  4.     <Columns>
  5.         <asp:BoundField DataField="ProductID" HeaderText="ID" ReadOnly="True" />
  6.         <asp:BoundField DataField="ProductName" HeaderText="产品名称" />
  7.         <asp:BoundField DataField="UnitPrice" HeaderText="单价" DataFormatString="{0:c}" />
  8.         <asp:TemplateField HeaderText="操作">
  9.             <ItemTemplate>
  10.                 <asp:Button ID="btnEdit" runat="server" Text="编辑"
  11.                     CommandName="EditProduct" CommandArgument='<%# Eval("ProductID") %>' />
  12.                 <asp:Button ID="btnDelete" runat="server" Text="删除"
  13.                     CommandName="DeleteProduct" CommandArgument='<%# Eval("ProductID") %>'
  14.                     OnClientClick="return confirm('确定要删除此产品吗?');" />
  15.             </ItemTemplate>
  16.         </asp:TemplateField>
  17.     </Columns>
  18. </asp:GridView>
复制代码

3. 视图状态管理

Web Forms通过视图状态(ViewState)自动维护页面和控件的状态,解决了HTTP无状态协议带来的挑战。这使得开发者可以在页面回发之间保留数据,而无需手动处理。
  1. // 示例:使用视图状态保存数据
  2. protected void Page_Load(object sender, EventArgs e)
  3. {
  4.     if (!IsPostBack)
  5.     {
  6.         // 首次加载时从数据库获取数据
  7.         DataTable dt = GetUserData();
  8.         ViewState["UserData"] = dt;
  9.         gvUsers.DataSource = dt;
  10.         gvUsers.DataBind();
  11.     }
  12.     else
  13.     {
  14.         // 回发时从视图状态恢复数据
  15.         DataTable dt = (DataTable)ViewState["UserData"];
  16.         gvUsers.DataSource = dt;
  17.         gvUsers.DataBind();
  18.     }
  19. }
复制代码

4. 母版页与主题

母版页(Master Pages)允许开发者创建统一的页面布局,而主题(Themes)和外观(Skins)则提供了一种集中管理控件样式的方式,确保整个应用程序的一致性。
  1. <%-- 示例:母版页定义 --%>
  2. <%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="WebApp.SiteMaster" %>
  3. <!DOCTYPE html>
  4. <html lang="zh">
  5. <head runat="server">
  6.     <meta charset="utf-8" />
  7.     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  8.     <title><%: Page.Title %> - 我的ASP.NET应用程序</title>
  9.     <asp:ContentPlaceHolder ID="HeadContent" runat="server">
  10.     </asp:ContentPlaceHolder>
  11.     <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
  12. </head>
  13. <body>
  14.     <form runat="server">
  15.         <div class="header">
  16.             <h1>我的应用程序</h1>
  17.         </div>
  18.         <div class="menu">
  19.             <asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" EnableViewState="false" IncludeStyleBlock="false" Orientation="Horizontal">
  20.                 <Items>
  21.                     <asp:MenuItem NavigateUrl="~/Default.aspx" Text="首页"/>
  22.                     <asp:MenuItem NavigateUrl="~/About.aspx" Text="关于"/>
  23.                     <asp:MenuItem NavigateUrl="~/Contact.aspx" Text="联系我们"/>
  24.                 </Items>
  25.             </asp:Menu>
  26.         </div>
  27.         <div class="main">
  28.             <asp:ContentPlaceHolder ID="MainContent" runat="server"/>
  29.         </div>
  30.         <div class="footer">
  31.             <p>&copy; <%: DateTime.Now.Year %> - 我的ASP.NET应用程序</p>
  32.         </div>
  33.     </form>
  34. </body>
  35. </html>
复制代码

5. 数据绑定与数据源控件

Web Forms提供了强大的数据绑定功能和多种数据源控件,如SqlDataSource、ObjectDataSource、EntityDataSource等,简化了数据访问和展示的复杂性。
  1. <%-- 示例:使用EntityDataSource和ListView进行数据绑定 --%>
  2. <asp:EntityDataSource ID="edsCategories" runat="server"
  3.     ConnectionString="name=NorthwindEntities"
  4.     DefaultContainerName="NorthwindEntities"
  5.     EntitySetName="Categories"
  6.     EnableFlattening="False">
  7. </asp:EntityDataSource>
  8. <asp:ListView ID="lvCategories" runat="server" DataKeyNames="CategoryID"
  9.     DataSourceID="edsCategories" InsertItemPosition="LastItem">
  10.     <LayoutTemplate>
  11.         <table runat="server" id="tblCategories">
  12.             <tr runat="server">
  13.                 <th runat="server">ID</th>
  14.                 <th runat="server">类别名称</th>
  15.                 <th runat="server">描述</th>
  16.                 <th runat="server">操作</th>
  17.             </tr>
  18.             <tr runat="server" id="itemPlaceholder" />
  19.         </table>
  20.     </LayoutTemplate>
  21.     <ItemTemplate>
  22.         <tr runat="server">
  23.             <td><asp:Label ID="lblCategoryID" runat="server" Text='<%# Eval("CategoryID") %>' /></td>
  24.             <td><asp:Label ID="lblCategoryName" runat="server" Text='<%# Eval("CategoryName") %>' /></td>
  25.             <td><asp:Label ID="lblDescription" runat="server" Text='<%# Eval("Description") %>' /></td>
  26.             <td>
  27.                 <asp:Button ID="btnEdit" runat="server" CommandName="Edit" Text="编辑" />
  28.                 <asp:Button ID="btnDelete" runat="server" CommandName="Delete" Text="删除"
  29.                     OnClientClick="return confirm('确定要删除此类别吗?');" />
  30.             </td>
  31.         </tr>
  32.     </ItemTemplate>
  33.     <EditItemTemplate>
  34.         <tr runat="server">
  35.             <td><asp:Label ID="lblCategoryID" runat="server" Text='<%# Eval("CategoryID") %>' /></td>
  36.             <td><asp:TextBox ID="txtCategoryName" runat="server" Text='<%# Bind("CategoryName") %>' /></td>
  37.             <td><asp:TextBox ID="txtDescription" runat="server" Text='<%# Bind("Description") %>' TextMode="MultiLine" /></td>
  38.             <td>
  39.                 <asp:Button ID="btnUpdate" runat="server" CommandName="Update" Text="更新" />
  40.                 <asp:Button ID="btnCancel" runat="server" CommandName="Cancel" Text="取消" />
  41.             </td>
  42.         </tr>
  43.     </EditItemTemplate>
  44.     <InsertItemTemplate>
  45.         <tr runat="server">
  46.             <td> </td>
  47.             <td><asp:TextBox ID="txtCategoryName" runat="server" Text='<%# Bind("CategoryName") %>' /></td>
  48.             <td><asp:TextBox ID="txtDescription" runat="server" Text='<%# Bind("Description") %>' TextMode="MultiLine" /></td>
  49.             <td>
  50.                 <asp:Button ID="btnInsert" runat="server" CommandName="Insert" Text="插入" />
  51.                 <asp:Button ID="btnClear" runat="server" CommandName="Cancel" Text="清除" />
  52.             </td>
  53.         </tr>
  54.     </InsertItemTemplate>
  55. </asp:ListView>
复制代码

实际应用技巧

1. 优化视图状态使用

视图状态虽然方便,但会增加页面大小,影响性能。以下是一些优化技巧:
  1. // 示例:禁用不必要控件的视图状态
  2. protected void Page_Init(object sender, EventArgs e)
  3. {
  4.     // 禁用整个页面的视图状态
  5.     this.EnableViewState = false;
  6.    
  7.     // 或者只禁用特定控件的视图状态
  8.     lblMessage.EnableViewState = false;
  9.     gvUsers.EnableViewState = true; // 保留需要视图状态的控件
  10. }
  11. // 示例:使用会话状态替代视图状态存储大量数据
  12. protected void Page_Load(object sender, EventArgs e)
  13. {
  14.     if (!IsPostBack)
  15.     {
  16.         // 获取大量数据
  17.         DataTable largeDataSet = GetLargeDataSet();
  18.         
  19.         // 存储在会话状态而非视图状态中
  20.         Session["LargeDataSet"] = largeDataSet;
  21.         
  22.         // 绑定数据
  23.         gvLargeData.DataSource = largeDataSet;
  24.         gvLargeData.DataBind();
  25.     }
  26.     else
  27.     {
  28.         // 从会话状态恢复数据
  29.         DataTable largeDataSet = (DataTable)Session["LargeDataSet"];
  30.         gvLargeData.DataSource = largeDataSet;
  31.         gvLargeData.DataBind();
  32.     }
  33. }
复制代码

2. 实现自定义服务器控件

创建自定义服务器控件可以提高代码重用性和开发效率:
  1. // 示例:自定义日期选择器控件
  2. using System;
  3. using System.Web.UI;
  4. using System.Web.UI.WebControls;
  5. namespace CustomControls
  6. {
  7.     [ToolboxData("<{0}:DatePicker runat=server></{0}:DatePicker>")]
  8.     public class DatePicker : TextBox
  9.     {
  10.         protected override void OnPreRender(EventArgs e)
  11.         {
  12.             base.OnPreRender(e);
  13.             
  14.             // 添加jQuery UI日期选择器所需的CSS和JavaScript
  15.             Page.ClientScript.RegisterClientScriptInclude("jQuery",
  16.                 "https://code.jquery.com/jquery-3.6.0.min.js");
  17.             Page.ClientScript.RegisterClientScriptInclude("jQueryUI",
  18.                 "https://code.jquery.com/ui/1.13.0/jquery-ui.min.js");
  19.             Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "jQueryUICSS",
  20.                 "<link rel='stylesheet' href='https://code.jquery.com/ui/1.13.0/themes/base/jquery-ui.css'>");
  21.             
  22.             // 初始化日期选择器
  23.             string script = string.Format("$(function() {{ $('#{0}').datepicker(); }});", this.ClientID);
  24.             Page.ClientScript.RegisterStartupScript(this.GetType(), "DatePicker_" + this.ClientID, script, true);
  25.         }
  26.     }
  27. }
复制代码

使用自定义控件:
  1. <%-- 注册自定义控件 --%>
  2. <%@ Register Assembly="CustomControls" Namespace="CustomControls" TagPrefix="cc" %>
  3. <%-- 使用自定义日期选择器 --%>
  4. <cc:DatePicker ID="dpBirthDate" runat="server" />
复制代码

3. 使用用户控件(UserControl)实现模块化开发

用户控件是Web Forms中实现模块化开发的重要工具:
  1. <%-- 示例:产品搜索用户控件 (ProductSearch.ascx) --%>
  2. <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductSearch.ascx.cs"
  3.     Inherits="WebApp.Controls.ProductSearch" %>
  4. <div class="product-search">
  5.     <h3>产品搜索</h3>
  6.     <div class="search-form">
  7.         <asp:TextBox ID="txtKeyword" runat="server" placeholder="输入关键词..." />
  8.         <asp:DropDownList ID="ddlCategory" runat="server">
  9.             <asp:ListItem Value="">所有类别</asp:ListItem>
  10.             <asp:ListItem Value="1">电子产品</asp:ListItem>
  11.             <asp:ListItem Value="2">服装</asp:ListItem>
  12.             <asp:ListItem Value="3">食品</asp:ListItem>
  13.         </asp:DropDownList>
  14.         <asp:Button ID="btnSearch" runat="server" Text="搜索" OnClick="btnSearch_Click" />
  15.     </div>
  16.    
  17.     <asp:Panel ID="pnlResults" runat="server" Visible="false">
  18.         <h4>搜索结果</h4>
  19.         <asp:Repeater ID="rptResults" runat="server">
  20.             <HeaderTemplate>
  21.                 <ul class="product-list">
  22.             </HeaderTemplate>
  23.             <ItemTemplate>
  24.                 <li>
  25.                     <h5><%# Eval("ProductName") %></h5>
  26.                     <p><%# Eval("Description") %></p>
  27.                     <p class="price"><%# Eval("UnitPrice", "{0:c}") %></p>
  28.                 </li>
  29.             </ItemTemplate>
  30.             <FooterTemplate>
  31.                 </ul>
  32.             </FooterTemplate>
  33.         </asp:Repeater>
  34.     </asp:Panel>
  35. </div>
复制代码
  1. // 示例:产品搜索用户控件代码后置
  2. using System;
  3. using System.Data;
  4. using System.Web.UI;
  5. namespace WebApp.Controls
  6. {
  7.     public partial class ProductSearch : System.Web.UI.UserControl
  8.     {
  9.         // 定义搜索完成事件
  10.         public event EventHandler SearchCompleted;
  11.         
  12.         protected void Page_Load(object sender, EventArgs e)
  13.         {
  14.             // 页面加载逻辑
  15.         }
  16.         
  17.         protected void btnSearch_Click(object sender, EventArgs e)
  18.         {
  19.             // 获取搜索参数
  20.             string keyword = txtKeyword.Text.Trim();
  21.             string categoryId = ddlCategory.SelectedValue;
  22.             
  23.             // 执行搜索
  24.             DataTable results = SearchProducts(keyword, categoryId);
  25.             
  26.             // 绑定结果
  27.             rptResults.DataSource = results;
  28.             rptResults.DataBind();
  29.             
  30.             // 显示结果面板
  31.             pnlResults.Visible = true;
  32.             
  33.             // 触发搜索完成事件
  34.             if (SearchCompleted != null)
  35.             {
  36.                 SearchCompleted(this, EventArgs.Empty);
  37.             }
  38.         }
  39.         
  40.         private DataTable SearchProducts(string keyword, string categoryId)
  41.         {
  42.             // 实际应用中,这里应该调用业务逻辑层或数据访问层
  43.             // 这里只是示例代码
  44.             DataTable dt = new DataTable();
  45.             dt.Columns.Add("ProductID", typeof(int));
  46.             dt.Columns.Add("ProductName", typeof(string));
  47.             dt.Columns.Add("Description", typeof(string));
  48.             dt.Columns.Add("UnitPrice", typeof(decimal));
  49.             
  50.             // 模拟数据
  51.             dt.Rows.Add(1, "笔记本电脑", "高性能笔记本电脑", 5999.99m);
  52.             dt.Rows.Add(2, "智能手机", "最新款智能手机", 3999.99m);
  53.             dt.Rows.Add(3, "平板电脑", "轻薄便携平板电脑", 2999.99m);
  54.             
  55.             return dt;
  56.         }
  57.     }
  58. }
复制代码

在页面中使用用户控件:
  1. <%-- 注册用户控件 --%>
  2. <%@ Register Src="~/Controls/ProductSearch.ascx" TagPrefix="uc" TagName="ProductSearch" %>
  3. <%-- 使用用户控件 --%>
  4. <uc:ProductSearch ID="productSearch1" runat="server" OnSearchCompleted="productSearch1_SearchCompleted" />
复制代码

4. 实现AJAX功能增强用户体验

使用ASP.NET AJAX可以创建更响应式的用户界面:
  1. <%-- 示例:使用UpdatePanel实现部分页面更新 --%>
  2. <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
  3. <asp:UpdatePanel ID="UpdatePanel1" runat="server">
  4.     <ContentTemplate>
  5.         <div class="product-filter">
  6.             <h3>产品筛选</h3>
  7.             <div class="filter-options">
  8.                 <asp:DropDownList ID="ddlCategory" runat="server" AutoPostBack="true"
  9.                     OnSelectedIndexChanged="ddlCategory_SelectedIndexChanged">
  10.                     <asp:ListItem Value="">所有类别</asp:ListItem>
  11.                     <asp:ListItem Value="1">电子产品</asp:ListItem>
  12.                     <asp:ListItem Value="2">服装</asp:ListItem>
  13.                     <asp:ListItem Value="3">食品</asp:ListItem>
  14.                 </asp:DropDownList>
  15.                
  16.                 <asp:DropDownList ID="ddlSortBy" runat="server" AutoPostBack="true"
  17.                     OnSelectedIndexChanged="ddlSortBy_SelectedIndexChanged">
  18.                     <asp:ListItem Value="name">按名称排序</asp:ListItem>
  19.                     <asp:ListItem Value="price">按价格排序</asp:ListItem>
  20.                     <asp:ListItem Value="date">按日期排序</asp:ListItem>
  21.                 </asp:DropDownList>
  22.             </div>
  23.             
  24.             <div class="product-list">
  25.                 <asp:ListView ID="lvProducts" runat="server">
  26.                     <LayoutTemplate>
  27.                         <div class="products">
  28.                             <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
  29.                         </div>
  30.                     </LayoutTemplate>
  31.                     <ItemTemplate>
  32.                         <div class="product-item">
  33.                             <h4><%# Eval("ProductName") %></h4>
  34.                             <p><%# Eval("Description") %></p>
  35.                             <p class="price"><%# Eval("UnitPrice", "{0:c}") %></p>
  36.                             <asp:Button ID="btnAddToCart" runat="server" Text="加入购物车"
  37.                                 CommandArgument='<%# Eval("ProductID") %>' OnClick="btnAddToCart_Click" />
  38.                         </div>
  39.                     </ItemTemplate>
  40.                     <EmptyDataTemplate>
  41.                         <p>没有找到符合条件的产品。</p>
  42.                     </EmptyDataTemplate>
  43.                 </asp:ListView>
  44.             </div>
  45.             
  46.             <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
  47.                 <ProgressTemplate>
  48.                     <div class="loading">
  49.                         <p>加载中,请稍候...</p>
  50.                     </div>
  51.                 </ProgressTemplate>
  52.             </asp:UpdateProgress>
  53.         </div>
  54.     </ContentTemplate>
  55. </asp:UpdatePanel>
  56. <%-- 显示添加到购物车的消息 --%>
  57. <asp:UpdatePanel ID="UpdatePanel2" runat="server">
  58.     <ContentTemplate>
  59.         <asp:Label ID="lblCartMessage" runat="server" CssClass="cart-message" />
  60.     </ContentTemplate>
  61. </asp:UpdatePanel>
复制代码
  1. // 示例:UpdatePanel相关代码
  2. protected void Page_Load(object sender, EventArgs e)
  3. {
  4.     if (!IsPostBack)
  5.     {
  6.         BindProducts();
  7.     }
  8. }
  9. private void BindProducts()
  10. {
  11.     string category = ddlCategory.SelectedValue;
  12.     string sortBy = ddlSortBy.SelectedValue;
  13.    
  14.     // 获取产品数据
  15.     DataTable products = GetProducts(category, sortBy);
  16.    
  17.     // 绑定到ListView
  18.     lvProducts.DataSource = products;
  19.     lvProducts.DataBind();
  20. }
  21. protected void ddlCategory_SelectedIndexChanged(object sender, EventArgs e)
  22. {
  23.     BindProducts();
  24. }
  25. protected void ddlSortBy_SelectedIndexChanged(object sender, EventArgs e)
  26. {
  27.     BindProducts();
  28. }
  29. protected void btnAddToCart_Click(object sender, EventArgs e)
  30. {
  31.     Button btn = (Button)sender;
  32.     int productId = Convert.ToInt32(btn.CommandArgument);
  33.    
  34.     // 添加到购物车的逻辑
  35.     AddToCart(productId);
  36.    
  37.     // 显示成功消息
  38.     lblCartMessage.Text = "产品已成功添加到购物车!";
  39.     lblCartMessage.ForeColor = System.Drawing.Color.Green;
  40.    
  41.     // 3秒后清除消息
  42.     ScriptManager.RegisterStartupScript(this, this.GetType(), "ClearMessage",
  43.         "setTimeout(function() { document.getElementById('" + lblCartMessage.ClientID + "').innerHTML = ''; }, 3000);", true);
  44. }
  45. private DataTable GetProducts(string category, string sortBy)
  46. {
  47.     // 实际应用中,这里应该调用业务逻辑层或数据访问层
  48.     // 这里只是示例代码
  49.     DataTable dt = new DataTable();
  50.     dt.Columns.Add("ProductID", typeof(int));
  51.     dt.Columns.Add("ProductName", typeof(string));
  52.     dt.Columns.Add("Description", typeof(string));
  53.     dt.Columns.Add("UnitPrice", typeof(decimal));
  54.    
  55.     // 模拟数据
  56.     dt.Rows.Add(1, "笔记本电脑", "高性能笔记本电脑", 5999.99m);
  57.     dt.Rows.Add(2, "智能手机", "最新款智能手机", 3999.99m);
  58.     dt.Rows.Add(3, "平板电脑", "轻薄便携平板电脑", 2999.99m);
  59.     dt.Rows.Add(4, "T恤", "纯棉舒适T恤", 99.99m);
  60.     dt.Rows.Add(5, "牛仔裤", "经典款牛仔裤", 199.99m);
  61.     dt.Rows.Add(6, "巧克力", "进口巧克力", 39.99m);
  62.    
  63.     // 应用筛选
  64.     if (!string.IsNullOrEmpty(category))
  65.     {
  66.         // 根据类别筛选数据
  67.         // 这里只是示例,实际应用中应该在数据库层面进行筛选
  68.     }
  69.    
  70.     // 应用排序
  71.     switch (sortBy)
  72.     {
  73.         case "name":
  74.             dt.DefaultView.Sort = "ProductName ASC";
  75.             break;
  76.         case "price":
  77.             dt.DefaultView.Sort = "UnitPrice ASC";
  78.             break;
  79.         case "date":
  80.             // 实际应用中应该有日期字段
  81.             break;
  82.     }
  83.    
  84.     return dt;
  85. }
  86. private void AddToCart(int productId)
  87. {
  88.     // 实际应用中,这里应该调用购物车业务逻辑
  89.     // 这里只是示例代码
  90.     if (Session["Cart"] == null)
  91.     {
  92.         Session["Cart"] = new System.Collections.Generic.List<int>();
  93.     }
  94.    
  95.     var cart = (System.Collections.Generic.List<int>)Session["Cart"];
  96.     cart.Add(productId);
  97. }
复制代码

5. 实现URL路由优化SEO

ASP.NET Web Forms 4.0及更高版本支持URL路由,可以创建更友好的URL:
  1. // 示例:在Global.asax中配置路由
  2. void Application_Start(object sender, EventArgs e)
  3. {
  4.     // 注册路由
  5.     RegisterRoutes(RouteTable.Routes);
  6. }
  7. void RegisterRoutes(RouteCollection routes)
  8. {
  9.     // 产品详情页路由
  10.     routes.MapPageRoute(
  11.         "ProductDetails",
  12.         "products/{productId}/{productName}",
  13.         "~/ProductDetails.aspx"
  14.     );
  15.    
  16.     // 分类页路由
  17.     routes.MapPageRoute(
  18.         "Category",
  19.         "category/{categoryId}/{categoryName}",
  20.         "~/Category.aspx"
  21.     );
  22.    
  23.     // 搜索页路由
  24.     routes.MapPageRoute(
  25.         "Search",
  26.         "search/{keyword}",
  27.         "~/SearchResults.aspx"
  28.     );
  29. }
复制代码

在页面中获取路由参数:
  1. // 示例:在ProductDetails.aspx中获取路由参数
  2. protected void Page_Load(object sender, EventArgs e)
  3. {
  4.     if (!IsPostBack)
  5.     {
  6.         // 从路由数据中获取产品ID
  7.         string productId = Page.RouteData.Values["productId"] as string;
  8.         
  9.         if (!string.IsNullOrEmpty(productId))
  10.         {
  11.             // 加载产品详情
  12.             LoadProductDetails(productId);
  13.         }
  14.         else
  15.         {
  16.             // 如果没有产品ID,重定向到错误页面或首页
  17.             Response.Redirect("~/Default.aspx");
  18.         }
  19.     }
  20. }
  21. private void LoadProductDetails(string productId)
  22. {
  23.     // 实际应用中,这里应该调用业务逻辑层或数据访问层
  24.     // 这里只是示例代码
  25.    
  26.     // 模拟产品数据
  27.     var product = new
  28.     {
  29.         ProductID = Convert.ToInt32(productId),
  30.         ProductName = "高性能笔记本电脑",
  31.         Description = "这款笔记本电脑配备最新的处理器和显卡,适合游戏和专业工作。",
  32.         UnitPrice = 5999.99m,
  33.         InStock = true,
  34.         ImageUrl = "~/images/products/laptop.jpg"
  35.     };
  36.    
  37.     // 绑定到页面控件
  38.     lblProductName.Text = product.ProductName;
  39.     lblDescription.Text = product.Description;
  40.     lblPrice.Text = product.UnitPrice.ToString("c");
  41.     lblStockStatus.Text = product.InStock ? "有货" : "缺货";
  42.     imgProduct.ImageUrl = product.ImageUrl;
  43.    
  44.     // 设置页面标题
  45.     Page.Title = product.ProductName + " - 我的产品网站";
  46. }
复制代码

创建友好链接:
  1. <%-- 示例:在产品列表中创建友好链接 --%>
  2. <asp:ListView ID="lvProducts" runat="server">
  3.     <LayoutTemplate>
  4.         <div class="product-list">
  5.             <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
  6.         </div>
  7.     </LayoutTemplate>
  8.     <ItemTemplate>
  9.         <div class="product-item">
  10.             <h3>
  11.                 <asp:HyperLink ID="lnkProduct" runat="server"
  12.                     Text='<%# Eval("ProductName") %>'
  13.                     NavigateUrl='<%# GetProductUrl(Eval("ProductID"), Eval("ProductName")) %>' />
  14.             </h3>
  15.             <p><%# Eval("Description") %></p>
  16.             <p class="price"><%# Eval("UnitPrice", "{0:c}") %></p>
  17.         </div>
  18.     </ItemTemplate>
  19. </asp:ListView>
复制代码
  1. // 示例:代码后置中的辅助方法
  2. protected string GetProductUrl(object productId, object productName)
  3. {
  4.     // 创建URL友好的产品名称
  5.     string name = productName.ToString();
  6.     name = name.ToLower();
  7.     name = Regex.Replace(name, @"\s+", "-"); // 替换空格为连字符
  8.     name = Regex.Replace(name, @"[^\w\-]+", ""); // 移除非字母数字连字符的字符
  9.     name = Regex.Replace(name, @"\-{2,}", "-"); // 替换多个连字符为单个连字符
  10.     name = name.Trim('-'); // 移除开头和结尾的连字符
  11.    
  12.     // 返回路由URL
  13.     return GetRouteUrl("ProductDetails", new {
  14.         productId = productId,
  15.         productName = name
  16.     });
  17. }
复制代码

解决开发中的常见挑战

1. 处理页面生命周期问题

Web Forms的页面生命周期复杂,理解并正确处理各个阶段是开发稳定应用的关键:
  1. // 示例:正确处理页面生命周期事件
  2. public partial class ProductPage : System.Web.UI.Page
  3. {
  4.     private Product _currentProduct;
  5.    
  6.     // PreInit阶段:可以动态设置母版页或主题
  7.     protected void Page_PreInit(object sender, EventArgs e)
  8.     {
  9.         // 根据用户选择动态设置主题
  10.         if (Session["SelectedTheme"] != null)
  11.         {
  12.             Page.Theme = Session["SelectedTheme"].ToString();
  13.         }
  14.     }
  15.    
  16.     // Init阶段:初始化控件,但视图状态尚未加载
  17.     protected void Page_Init(object sender, EventArgs e)
  18.     {
  19.         // 动态创建控件
  20.         Button btnDynamic = new Button();
  21.         btnDynamic.ID = "btnDynamic";
  22.         btnDynamic.Text = "动态按钮";
  23.         btnDynamic.Click += btnDynamic_Click;
  24.         phDynamic.Controls.Add(btnDynamic);
  25.     }
  26.    
  27.     // Load阶段:视图状态已加载,可以访问控件属性
  28.     protected void Page_Load(object sender, EventArgs e)
  29.     {
  30.         // 首次加载时初始化数据
  31.         if (!IsPostBack)
  32.         {
  33.             // 从URL参数获取产品ID
  34.             string productId = Request.QueryString["id"];
  35.             
  36.             if (!string.IsNullOrEmpty(productId))
  37.             {
  38.                 // 加载产品数据
  39.                 _currentProduct = ProductManager.GetProductById(int.Parse(productId));
  40.                
  41.                 // 绑定数据到控件
  42.                 BindProductData();
  43.             }
  44.         }
  45.     }
  46.    
  47.     // LoadComplete阶段:所有控件都已加载
  48.     protected void Page_LoadComplete(object sender, EventArgs e)
  49.     {
  50.         // 在这里执行需要在所有控件加载完成后才能进行的操作
  51.     }
  52.    
  53.     // PreRender阶段:页面即将呈现,是最后修改控件状态的机会
  54.     protected void Page_PreRender(object sender, EventArgs e)
  55.     {
  56.         // 根据用户权限设置按钮可见性
  57.         if (!User.IsInRole("Administrator"))
  58.         {
  59.             btnDelete.Visible = false;
  60.             btnEdit.Visible = false;
  61.         }
  62.     }
  63.    
  64.     // 动态按钮点击事件处理
  65.     protected void btnDynamic_Click(object sender, EventArgs e)
  66.     {
  67.         // 处理动态按钮点击
  68.         lblMessage.Text = "动态按钮被点击了!";
  69.     }
  70.    
  71.     // 保存产品按钮点击事件
  72.     protected void btnSave_Click(object sender, EventArgs e)
  73.     {
  74.         try
  75.         {
  76.             // 收集表单数据
  77.             Product product = new Product();
  78.             product.ProductID = int.Parse(hdnProductID.Value);
  79.             product.ProductName = txtProductName.Text;
  80.             product.Description = txtDescription.Text;
  81.             product.UnitPrice = decimal.Parse(txtUnitPrice.Text);
  82.             
  83.             // 保存产品
  84.             ProductManager.SaveProduct(product);
  85.             
  86.             // 显示成功消息
  87.             lblMessage.Text = "产品保存成功!";
  88.             lblMessage.CssClass = "success";
  89.         }
  90.         catch (Exception ex)
  91.         {
  92.             // 记录错误
  93.             Logger.Error("保存产品时出错", ex);
  94.             
  95.             // 显示错误消息
  96.             lblMessage.Text = "保存产品时出错:" + ex.Message;
  97.             lblMessage.CssClass = "error";
  98.         }
  99.     }
  100.    
  101.     private void BindProductData()
  102.     {
  103.         if (_currentProduct != null)
  104.         {
  105.             hdnProductID.Value = _currentProduct.ProductID.ToString();
  106.             txtProductName.Text = _currentProduct.ProductName;
  107.             txtDescription.Text = _currentProduct.Description;
  108.             txtUnitPrice.Text = _currentProduct.UnitPrice.ToString();
  109.         }
  110.     }
  111. }
复制代码

2. 处理并发更新冲突

在多用户环境中,处理并发更新冲突是一个常见挑战:
  1. // 示例:使用行版本控制处理并发冲突
  2. public class ProductManager
  3. {
  4.     public static Product GetProductById(int productId)
  5.     {
  6.         using (var context = new NorthwindEntities())
  7.         {
  8.             return context.Products.FirstOrDefault(p => p.ProductID == productId);
  9.         }
  10.     }
  11.    
  12.     public static bool UpdateProduct(Product product)
  13.     {
  14.         using (var context = new NorthwindEntities())
  15.         {
  16.             try
  17.             {
  18.                 // 获取数据库中的原始产品
  19.                 var originalProduct = context.Products.FirstOrDefault(p => p.ProductID == product.ProductID);
  20.                
  21.                 if (originalProduct == null)
  22.                 {
  23.                     return false; // 产品不存在
  24.                 }
  25.                
  26.                 // 检查并发冲突
  27.                 if (originalProduct.RowVersion != product.RowVersion)
  28.                 {
  29.                     // 行版本不匹配,表示数据已被其他用户修改
  30.                     return false;
  31.                 }
  32.                
  33.                 // 更新产品属性
  34.                 originalProduct.ProductName = product.ProductName;
  35.                 originalProduct.Description = product.Description;
  36.                 originalProduct.UnitPrice = product.UnitPrice;
  37.                
  38.                 // 保存更改
  39.                 context.SaveChanges();
  40.                
  41.                 return true;
  42.             }
  43.             catch (DbUpdateConcurrencyException)
  44.             {
  45.                 // 处理并发冲突
  46.                 return false;
  47.             }
  48.             catch (Exception ex)
  49.             {
  50.                 // 记录其他错误
  51.                 Logger.Error("更新产品时出错", ex);
  52.                 throw;
  53.             }
  54.         }
  55.     }
  56. }
  57. // 在页面中使用
  58. protected void btnSave_Click(object sender, EventArgs e)
  59. {
  60.     try
  61.     {
  62.         // 创建产品对象
  63.         Product product = new Product();
  64.         product.ProductID = int.Parse(hdnProductID.Value);
  65.         product.ProductName = txtProductName.Text;
  66.         product.Description = txtDescription.Text;
  67.         product.UnitPrice = decimal.Parse(txtUnitPrice.Text);
  68.         product.RowVersion = Convert.FromBase64String(hdnRowVersion.Value);
  69.         
  70.         // 尝试更新产品
  71.         bool success = ProductManager.UpdateProduct(product);
  72.         
  73.         if (success)
  74.         {
  75.             // 更新成功
  76.             lblMessage.Text = "产品更新成功!";
  77.             lblMessage.CssClass = "success";
  78.             
  79.             // 重新加载产品数据以获取最新的行版本
  80.             LoadProductData(product.ProductID);
  81.         }
  82.         else
  83.         {
  84.             // 更新失败,可能是并发冲突
  85.             lblMessage.Text = "更新失败:该产品已被其他用户修改。请刷新页面并重试。";
  86.             lblMessage.CssClass = "error";
  87.             
  88.             // 重新加载产品数据以显示最新值
  89.             LoadProductData(product.ProductID);
  90.         }
  91.     }
  92.     catch (Exception ex)
  93.     {
  94.         // 处理其他错误
  95.         lblMessage.Text = "更新产品时出错:" + ex.Message;
  96.         lblMessage.CssClass = "error";
  97.         Logger.Error("保存产品时出错", ex);
  98.     }
  99. }
  100. private void LoadProductData(int productId)
  101. {
  102.     Product product = ProductManager.GetProductById(productId);
  103.    
  104.     if (product != null)
  105.     {
  106.         hdnProductID.Value = product.ProductID.ToString();
  107.         txtProductName.Text = product.ProductName;
  108.         txtDescription.Text = product.Description;
  109.         txtUnitPrice.Text = product.UnitPrice.ToString();
  110.         
  111.         // 保存行版本到隐藏字段
  112.         hdnRowVersion.Value = Convert.ToBase64String(product.RowVersion);
  113.     }
  114. }
复制代码

3. 处理大型文件上传

处理大型文件上传是Web开发中的常见挑战,以下是一个解决方案:
  1. <%-- 示例:大型文件上传页面 --%>
  2. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="FileUpload.aspx.cs"
  3.     Inherits="WebApp.FileUpload" %>
  4. <!DOCTYPE html>
  5. <html>
  6. <head runat="server">
  7.     <title>文件上传</title>
  8.     <style>
  9.         .upload-container {
  10.             max-width: 600px;
  11.             margin: 0 auto;
  12.             padding: 20px;
  13.             border: 1px solid #ccc;
  14.             border-radius: 5px;
  15.         }
  16.         
  17.         .progress-container {
  18.             margin-top: 20px;
  19.             display: none;
  20.         }
  21.         
  22.         .progress-bar {
  23.             height: 20px;
  24.             background-color: #f0f0f0;
  25.             border-radius: 10px;
  26.             overflow: hidden;
  27.         }
  28.         
  29.         .progress {
  30.             height: 100%;
  31.             background-color: #4CAF50;
  32.             width: 0%;
  33.             transition: width 0.3s;
  34.         }
  35.         
  36.         .progress-text {
  37.             margin-top: 5px;
  38.             text-align: center;
  39.         }
  40.     </style>
  41. </head>
  42. <body>
  43.     <form id="form1" runat="server">
  44.         <div class="upload-container">
  45.             <h2>文件上传</h2>
  46.             
  47.             <div class="form-group">
  48.                 <label for="fuFile">选择文件:</label>
  49.                 <asp:FileUpload ID="fuFile" runat="server" />
  50.                 <asp:RequiredFieldValidator ID="rfvFile" runat="server"
  51.                     ControlToValidate="fuFile" ErrorMessage="请选择文件"
  52.                     Display="Dynamic" ValidationGroup="Upload" />
  53.                 <asp:CustomValidator ID="cvFileSize" runat="server"
  54.                     ControlToValidate="fuFile" ErrorMessage="文件大小不能超过100MB"
  55.                     Display="Dynamic" OnServerValidate="cvFileSize_ServerValidate"
  56.                     ValidationGroup="Upload" />
  57.             </div>
  58.             
  59.             <div class="form-group">
  60.                 <label for="txtDescription">文件描述:</label>
  61.                 <asp:TextBox ID="txtDescription" runat="server" TextMode="MultiLine" Rows="3" />
  62.             </div>
  63.             
  64.             <div class="form-group">
  65.                 <asp:Button ID="btnUpload" runat="server" Text="上传"
  66.                     OnClick="btnUpload_Click" ValidationGroup="Upload" />
  67.                 <asp:Button ID="btnCancel" runat="server" Text="取消"
  68.                     OnClick="btnCancel_Click" CausesValidation="false" />
  69.             </div>
  70.             
  71.             <div id="progressContainer" class="progress-container">
  72.                 <div class="progress-bar">
  73.                     <div id="progress" class="progress"></div>
  74.                 </div>
  75.                 <div id="progressText" class="progress-text">0%</div>
  76.             </div>
  77.             
  78.             <div class="message">
  79.                 <asp:Label ID="lblMessage" runat="server" />
  80.             </div>
  81.         </div>
  82.     </form>
  83.    
  84.     <script>
  85.         // 显示进度条
  86.         function showProgress() {
  87.             document.getElementById('progressContainer').style.display = 'block';
  88.         }
  89.         
  90.         // 更新进度条
  91.         function updateProgress(percent) {
  92.             document.getElementById('progress').style.width = percent + '%';
  93.             document.getElementById('progressText').innerText = percent + '%';
  94.         }
  95.         
  96.         // 隐藏进度条
  97.         function hideProgress() {
  98.             document.getElementById('progressContainer').style.display = 'none';
  99.         }
  100.     </script>
  101. </body>
  102. </html>
复制代码
  1. // 示例:文件上传代码后置
  2. using System;
  3. using System.IO;
  4. using System.Web;
  5. using System.Web.UI;
  6. namespace WebApp
  7. {
  8.     public partial class FileUpload : System.Web.UI.Page
  9.     {
  10.         // 最大文件大小(100MB)
  11.         private const int MaxFileSize = 100 * 1024 * 1024;
  12.         
  13.         protected void Page_Load(object sender, EventArgs e)
  14.         {
  15.             // 设置页面最大请求长度
  16.             // 注意:这需要在web.config中也设置maxRequestLength
  17.         }
  18.         
  19.         protected void cvFileSize_ServerValidate(object source, ServerValidateEventArgs args)
  20.         {
  21.             if (fuFile.HasFile)
  22.             {
  23.                 // 检查文件大小
  24.                 if (fuFile.PostedFile.ContentLength > MaxFileSize)
  25.                 {
  26.                     args.IsValid = false;
  27.                 }
  28.                 else
  29.                 {
  30.                     args.IsValid = true;
  31.                 }
  32.             }
  33.             else
  34.             {
  35.                 args.IsValid = true; // 让RequiredFieldValidator处理空文件的情况
  36.             }
  37.         }
  38.         
  39.         protected void btnUpload_Click(object sender, EventArgs e)
  40.         {
  41.             if (Page.IsValid)
  42.             {
  43.                 try
  44.                 {
  45.                     if (fuFile.HasFile)
  46.                     {
  47.                         // 显示进度条
  48.                         ScriptManager.RegisterStartupScript(this, this.GetType(), "ShowProgress", "showProgress();", true);
  49.                         
  50.                         // 获取文件信息
  51.                         HttpPostedFile postedFile = fuFile.PostedFile;
  52.                         string fileName = Path.GetFileName(postedFile.FileName);
  53.                         string fileExtension = Path.GetExtension(fileName).ToLower();
  54.                         string contentType = postedFile.ContentType;
  55.                         int fileSize = postedFile.ContentLength;
  56.                         
  57.                         // 验证文件类型(可选)
  58.                         string[] allowedExtensions = { ".jpg", ".jpeg", ".png", ".gif", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".txt" };
  59.                         bool isAllowedExtension = false;
  60.                         
  61.                         foreach (string ext in allowedExtensions)
  62.                         {
  63.                             if (fileExtension == ext)
  64.                             {
  65.                                 isAllowedExtension = true;
  66.                                 break;
  67.                             }
  68.                         }
  69.                         
  70.                         if (!isAllowedExtension)
  71.                         {
  72.                             lblMessage.Text = "不允许的文件类型!";
  73.                             lblMessage.CssClass = "error";
  74.                             ScriptManager.RegisterStartupScript(this, this.GetType(), "HideProgress", "hideProgress();", true);
  75.                             return;
  76.                         }
  77.                         
  78.                         // 创建上传目录(如果不存在)
  79.                         string uploadDirectory = Server.MapPath("~/Uploads");
  80.                         if (!Directory.Exists(uploadDirectory))
  81.                         {
  82.                             Directory.CreateDirectory(uploadDirectory);
  83.                         }
  84.                         
  85.                         // 创建唯一文件名以避免覆盖
  86.                         string uniqueFileName = DateTime.Now.ToString("yyyyMMddHHmmssfff") + "_" + fileName;
  87.                         string filePath = Path.Combine(uploadDirectory, uniqueFileName);
  88.                         
  89.                         // 模拟进度更新(实际应用中可能需要更复杂的方法)
  90.                         for (int i = 0; i <= 100; i += 10)
  91.                         {
  92.                             // 更新进度条
  93.                             ScriptManager.RegisterStartupScript(this, this.GetType(), "UpdateProgress" + i,
  94.                                 "updateProgress(" + i + ");", true);
  95.                            
  96.                             // 模拟处理时间
  97.                             System.Threading.Thread.Sleep(200);
  98.                         }
  99.                         
  100.                         // 保存文件
  101.                         postedFile.SaveAs(filePath);
  102.                         
  103.                         // 保存文件信息到数据库(可选)
  104.                         SaveFileInfo(fileName, uniqueFileName, fileExtension, contentType, fileSize, txtDescription.Text);
  105.                         
  106.                         // 显示成功消息
  107.                         lblMessage.Text = "文件上传成功!";
  108.                         lblMessage.CssClass = "success";
  109.                         
  110.                         // 隐藏进度条
  111.                         ScriptManager.RegisterStartupScript(this, this.GetType(), "HideProgress", "hideProgress();", true);
  112.                         
  113.                         // 清空表单
  114.                         ClearForm();
  115.                     }
  116.                 }
  117.                 catch (Exception ex)
  118.                 {
  119.                     // 记录错误
  120.                     Logger.Error("文件上传时出错", ex);
  121.                     
  122.                     // 显示错误消息
  123.                     lblMessage.Text = "文件上传失败:" + ex.Message;
  124.                     lblMessage.CssClass = "error";
  125.                     
  126.                     // 隐藏进度条
  127.                     ScriptManager.RegisterStartupScript(this, this.GetType(), "HideProgress", "hideProgress();", true);
  128.                 }
  129.             }
  130.         }
  131.         
  132.         protected void btnCancel_Click(object sender, EventArgs e)
  133.         {
  134.             // 清空表单
  135.             ClearForm();
  136.             
  137.             // 清空消息
  138.             lblMessage.Text = "";
  139.         }
  140.         
  141.         private void SaveFileInfo(string originalFileName, string uniqueFileName, string fileExtension,
  142.             string contentType, int fileSize, string description)
  143.         {
  144.             // 实际应用中,这里应该将文件信息保存到数据库
  145.             // 这里只是示例代码
  146.             try
  147.             {
  148.                 // 模拟保存到数据库
  149.                 // FileRepository.Save(originalFileName, uniqueFileName, fileExtension, contentType, fileSize, description);
  150.             }
  151.             catch (Exception ex)
  152.             {
  153.                 Logger.Error("保存文件信息时出错", ex);
  154.                 throw;
  155.             }
  156.         }
  157.         
  158.         private void ClearForm()
  159.         {
  160.             // 清空文件上传控件
  161.             fuFile.FileContent.Dispose();
  162.             fuFile = new FileUpload();
  163.             
  164.             // 清空文本框
  165.             txtDescription.Text = "";
  166.         }
  167.     }
  168. }
复制代码

在web.config中配置最大请求长度:
  1. <system.web>
  2.   <!-- 设置最大请求长度(单位:KB),这里设置为100MB -->
  3.   <httpRuntime maxRequestLength="102400" executionTimeout="3600" />
  4.   
  5.   <!-- 对于IIS 7+,还需要设置以下内容 -->
  6.   <security>
  7.     <requestFiltering>
  8.       <!-- 设置最大允许的内容长度(单位:字节),这里设置为100MB -->
  9.       <requestLimits maxAllowedContentLength="104857600" />
  10.     </requestFiltering>
  11.   </security>
  12. </system.web>
复制代码

4. 实现高效分页

处理大量数据时,高效分页是提高性能的关键:
  1. // 示例:使用存储过程实现高效分页
  2. public class ProductRepository
  3. {
  4.     // 使用Entity Framework实现分页
  5.     public static List<Product> GetProductsPaged(int pageNumber, int pageSize, out int totalRecords)
  6.     {
  7.         using (var context = new NorthwindEntities())
  8.         {
  9.             // 获取总记录数
  10.             totalRecords = context.Products.Count();
  11.             
  12.             // 获取分页数据
  13.             var products = context.Products
  14.                 .OrderBy(p => p.ProductName)
  15.                 .Skip((pageNumber - 1) * pageSize)
  16.                 .Take(pageSize)
  17.                 .ToList();
  18.                
  19.             return products;
  20.         }
  21.     }
  22.    
  23.     // 使用ADO.NET和存储过程实现分页
  24.     public static DataTable GetProductsPagedWithStoredProcedure(int pageNumber, int pageSize, out int totalRecords)
  25.     {
  26.         DataTable dt = new DataTable();
  27.         
  28.         using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString))
  29.         {
  30.             using (var command = new SqlCommand("GetProductsPaged", connection))
  31.             {
  32.                 command.CommandType = CommandType.StoredProcedure;
  33.                
  34.                 // 添加参数
  35.                 command.Parameters.AddWithValue("@PageNumber", pageNumber);
  36.                 command.Parameters.AddWithValue("@PageSize", pageSize);
  37.                
  38.                 // 添加输出参数
  39.                 SqlParameter totalRecordsParam = new SqlParameter("@TotalRecords", SqlDbType.Int);
  40.                 totalRecordsParam.Direction = ParameterDirection.Output;
  41.                 command.Parameters.Add(totalRecordsParam);
  42.                
  43.                 using (var adapter = new SqlDataAdapter(command))
  44.                 {
  45.                     adapter.Fill(dt);
  46.                 }
  47.                
  48.                 // 获取总记录数
  49.                 totalRecords = (int)totalRecordsParam.Value;
  50.             }
  51.         }
  52.         
  53.         return dt;
  54.     }
  55. }
复制代码

存储过程示例(SQL Server):
  1. CREATE PROCEDURE GetProductsPaged
  2.     @PageNumber INT,
  3.     @PageSize INT,
  4.     @TotalRecords INT OUTPUT
  5. AS
  6. BEGIN
  7.     SET NOCOUNT ON;
  8.    
  9.     -- 获取总记录数
  10.     SELECT @TotalRecords = COUNT(*) FROM Products;
  11.    
  12.     -- 使用ROW_NUMBER()实现分页
  13.     WITH NumberedProducts AS (
  14.         SELECT
  15.             ProductID,
  16.             ProductName,
  17.             QuantityPerUnit,
  18.             UnitPrice,
  19.             UnitsInStock,
  20.             UnitsOnOrder,
  21.             ReorderLevel,
  22.             Discontinued,
  23.             ROW_NUMBER() OVER (ORDER BY ProductName) AS RowNumber
  24.         FROM
  25.             Products
  26.     )
  27.     SELECT
  28.         ProductID,
  29.         ProductName,
  30.         QuantityPerUnit,
  31.         UnitPrice,
  32.         UnitsInStock,
  33.         UnitsOnOrder,
  34.         ReorderLevel,
  35.         Discontinued
  36.     FROM
  37.         NumberedProducts
  38.     WHERE
  39.         RowNumber BETWEEN (@PageNumber - 1) * @PageSize + 1 AND @PageNumber * @PageSize;
  40. END
复制代码

在页面中实现分页:
  1. <%-- 示例:产品分页列表 --%>
  2. <asp:ListView ID="lvProducts" runat="server">
  3.     <LayoutTemplate>
  4.         <table class="product-table">
  5.             <thead>
  6.                 <tr>
  7.                     <th>产品ID</th>
  8.                     <th>产品名称</th>
  9.                     <th>单价</th>
  10.                     <th>库存</th>
  11.                     <th>操作</th>
  12.                 </tr>
  13.             </thead>
  14.             <tbody>
  15.                 <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
  16.             </tbody>
  17.         </table>
  18.     </LayoutTemplate>
  19.     <ItemTemplate>
  20.         <tr>
  21.             <td><%# Eval("ProductID") %></td>
  22.             <td><%# Eval("ProductName") %></td>
  23.             <td><%# Eval("UnitPrice", "{0:c}") %></td>
  24.             <td><%# Eval("UnitsInStock") %></td>
  25.             <td>
  26.                 <asp:HyperLink ID="lnkEdit" runat="server" Text="编辑"
  27.                     NavigateUrl='<%# "EditProduct.aspx?id=" + Eval("ProductID") %>' />
  28.             </td>
  29.         </tr>
  30.     </ItemTemplate>
  31.     <EmptyDataTemplate>
  32.         <p>没有找到产品。</p>
  33.     </EmptyDataTemplate>
  34. </asp:ListView>
  35. <div class="pagination">
  36.     <asp:DataPager ID="dpProducts" runat="server" PagedControlID="lvProducts" PageSize="10">
  37.         <Fields>
  38.             <asp:NextPreviousPagerField ButtonType="Button" ShowFirstPageButton="True"
  39.                 ShowNextPageButton="False" ShowPreviousPageButton="False" />
  40.             <asp:NumericPagerField />
  41.             <asp:NextPreviousPagerField ButtonType="Button" ShowLastPageButton="True"
  42.                 ShowNextPageButton="False" ShowPreviousPageButton="False" />
  43.         </Fields>
  44.     </asp:DataPager>
  45. </div>
  46. <div class="page-info">
  47.     <asp:Label ID="lblPageInfo" runat="server" />
  48. </div>
复制代码
  1. // 示例:分页代码后置
  2. public partial class ProductList : System.Web.UI.Page
  3. {
  4.     private int _pageNumber = 1;
  5.     private int _pageSize = 10;
  6.     private int _totalRecords = 0;
  7.    
  8.     protected void Page_Load(object sender, EventArgs e)
  9.     {
  10.         // 从查询字符串获取页码
  11.         if (!string.IsNullOrEmpty(Request.QueryString["page"]))
  12.         {
  13.             int.TryParse(Request.QueryString["page"], out _pageNumber);
  14.             if (_pageNumber < 1)
  15.             {
  16.                 _pageNumber = 1;
  17.             }
  18.         }
  19.         
  20.         if (!IsPostBack)
  21.         {
  22.             BindProducts();
  23.         }
  24.     }
  25.    
  26.     protected void lvProducts_PagePropertiesChanging(object sender, PagePropertiesChangingEventArgs e)
  27.     {
  28.         // 更新页码
  29.         _pageNumber = (e.StartRowIndex / e.MaximumRows) + 1;
  30.         
  31.         // 重新绑定数据
  32.         BindProducts();
  33.     }
  34.    
  35.     private void BindProducts()
  36.     {
  37.         try
  38.         {
  39.             // 获取分页数据
  40.             List<Product> products = ProductRepository.GetProductsPaged(_pageNumber, _pageSize, out _totalRecords);
  41.             
  42.             // 绑定到ListView
  43.             lvProducts.DataSource = products;
  44.             lvProducts.DataBind();
  45.             
  46.             // 设置DataPager属性
  47.             dpProducts.SetPageProperties((_pageNumber - 1) * _pageSize, _pageSize, true);
  48.             
  49.             // 显示分页信息
  50.             int totalPages = (int)Math.Ceiling((double)_totalRecords / _pageSize);
  51.             lblPageInfo.Text = string.Format("显示第 {0} - {1} 项,共 {2} 项(第 {3} 页,共 {4} 页)",
  52.                 (_pageNumber - 1) * _pageSize + 1,
  53.                 Math.Min(_pageNumber * _pageSize, _totalRecords),
  54.                 _totalRecords,
  55.                 _pageNumber,
  56.                 totalPages);
  57.         }
  58.         catch (Exception ex)
  59.         {
  60.             Logger.Error("绑定产品列表时出错", ex);
  61.             lblPageInfo.Text = "加载数据时出错:" + ex.Message;
  62.         }
  63.     }
  64. }
复制代码

5. 实现依赖注入提高可测试性

依赖注入(DI)是提高代码可测试性和可维护性的重要技术:
  1. // 示例:定义服务接口
  2. public interface IProductService
  3. {
  4.     Product GetProductById(int id);
  5.     List<Product> GetAllProducts();
  6.     void SaveProduct(Product product);
  7.     void DeleteProduct(int id);
  8. }
  9. // 示例:实现服务
  10. public class ProductService : IProductService
  11. {
  12.     private readonly IProductRepository _repository;
  13.    
  14.     public ProductService(IProductRepository repository)
  15.     {
  16.         _repository = repository;
  17.     }
  18.    
  19.     public Product GetProductById(int id)
  20.     {
  21.         return _repository.GetById(id);
  22.     }
  23.    
  24.     public List<Product> GetAllProducts()
  25.     {
  26.         return _repository.GetAll();
  27.     }
  28.    
  29.     public void SaveProduct(Product product)
  30.     {
  31.         if (product.ProductID == 0)
  32.         {
  33.             // 新产品
  34.             _repository.Add(product);
  35.         }
  36.         else
  37.         {
  38.             // 更新产品
  39.             _repository.Update(product);
  40.         }
  41.     }
  42.    
  43.     public void DeleteProduct(int id)
  44.     {
  45.         _repository.Delete(id);
  46.     }
  47. }
  48. // 示例:定义存储库接口
  49. public interface IProductRepository
  50. {
  51.     Product GetById(int id);
  52.     List<Product> GetAll();
  53.     void Add(Product product);
  54.     void Update(Product product);
  55.     void Delete(int id);
  56. }
  57. // 示例:实现存储库
  58. public class ProductRepository : IProductRepository
  59. {
  60.     private readonly NorthwindEntities _context;
  61.    
  62.     public ProductRepository(NorthwindEntities context)
  63.     {
  64.         _context = context;
  65.     }
  66.    
  67.     public Product GetById(int id)
  68.     {
  69.         return _context.Products.Find(id);
  70.     }
  71.    
  72.     public List<Product> GetAll()
  73.     {
  74.         return _context.Products.ToList();
  75.     }
  76.    
  77.     public void Add(Product product)
  78.     {
  79.         _context.Products.Add(product);
  80.         _context.SaveChanges();
  81.     }
  82.    
  83.     public void Update(Product product)
  84.     {
  85.         _context.Entry(product).State = EntityState.Modified;
  86.         _context.SaveChanges();
  87.     }
  88.    
  89.     public void Delete(int id)
  90.     {
  91.         Product product = _context.Products.Find(id);
  92.         if (product != null)
  93.         {
  94.             _context.Products.Remove(product);
  95.             _context.SaveChanges();
  96.         }
  97.     }
  98. }
复制代码

使用Unity容器实现依赖注入:
  1. // 示例:在Global.asax中配置依赖注入
  2. using Microsoft.Practices.Unity;
  3. using Microsoft.Practices.Unity.Configuration;
  4. public class Global : System.Web.HttpApplication
  5. {
  6.     private static IUnityContainer _container;
  7.    
  8.     public static IUnityContainer Container
  9.     {
  10.         get { return _container; }
  11.     }
  12.    
  13.     protected void Application_Start(object sender, EventArgs e)
  14.     {
  15.         // 创建Unity容器
  16.         _container = new UnityContainer();
  17.         
  18.         // 配置依赖注入
  19.         ConfigureDependencyInjection();
  20.         
  21.         // 注册路由等
  22.         RegisterRoutes(RouteTable.Routes);
  23.     }
  24.    
  25.     private void ConfigureDependencyInjection()
  26.     {
  27.         // 注册类型映射
  28.         _container.RegisterType<IProductService, ProductService>();
  29.         _container.RegisterType<IProductRepository, ProductRepository>();
  30.         _container.RegisterType<NorthwindEntities>(new HierarchicalLifetimeManager());
  31.         
  32.         // 设置依赖注入解析器
  33.         DependencyResolver.SetResolver(new UnityDependencyResolver(_container));
  34.     }
  35. }
  36. // 示例:自定义依赖注入解析器
  37. public class UnityDependencyResolver : IDependencyResolver
  38. {
  39.     private readonly IUnityContainer _container;
  40.    
  41.     public UnityDependencyResolver(IUnityContainer container)
  42.     {
  43.         _container = container;
  44.     }
  45.    
  46.     public object GetService(Type serviceType)
  47.     {
  48.         try
  49.         {
  50.             return _container.Resolve(serviceType);
  51.         }
  52.         catch
  53.         {
  54.             return null;
  55.         }
  56.     }
  57.    
  58.     public IEnumerable<object> GetServices(Type serviceType)
  59.     {
  60.         try
  61.         {
  62.             return _container.ResolveAll(serviceType);
  63.         }
  64.         catch
  65.         {
  66.             return new List<object>();
  67.         }
  68.     }
  69. }
  70. // 示例:在页面中使用依赖注入
  71. public partial class ProductDetails : System.Web.UI.Page
  72. {
  73.     private IProductService _productService;
  74.    
  75.     // 通过属性注入依赖
  76.     [Dependency]
  77.     public IProductService ProductService
  78.     {
  79.         get { return _productService; }
  80.         set { _productService = value; }
  81.     }
  82.    
  83.     protected void Page_Load(object sender, EventArgs e)
  84.     {
  85.         if (!IsPostBack)
  86.         {
  87.             // 从URL参数获取产品ID
  88.             string productId = Request.QueryString["id"];
  89.             
  90.             if (!string.IsNullOrEmpty(productId))
  91.             {
  92.                 // 使用服务获取产品
  93.                 Product product = _productService.GetProductById(int.Parse(productId));
  94.                
  95.                 if (product != null)
  96.                 {
  97.                     // 绑定产品数据到页面
  98.                     BindProductData(product);
  99.                 }
  100.                 else
  101.                 {
  102.                     // 产品不存在
  103.                     lblMessage.Text = "指定的产品不存在。";
  104.                     lblMessage.CssClass = "error";
  105.                 }
  106.             }
  107.             else
  108.             {
  109.                 // 没有提供产品ID
  110.                 lblMessage.Text = "请提供产品ID。";
  111.                 lblMessage.CssClass = "error";
  112.             }
  113.         }
  114.     }
  115.    
  116.     protected void btnSave_Click(object sender, EventArgs e)
  117.     {
  118.         try
  119.         {
  120.             // 创建产品对象
  121.             Product product = new Product();
  122.             product.ProductID = int.Parse(hdnProductID.Value);
  123.             product.ProductName = txtProductName.Text;
  124.             product.Description = txtDescription.Text;
  125.             product.UnitPrice = decimal.Parse(txtUnitPrice.Text);
  126.             
  127.             // 使用服务保存产品
  128.             _productService.SaveProduct(product);
  129.             
  130.             // 显示成功消息
  131.             lblMessage.Text = "产品保存成功!";
  132.             lblMessage.CssClass = "success";
  133.         }
  134.         catch (Exception ex)
  135.         {
  136.             // 记录错误
  137.             Logger.Error("保存产品时出错", ex);
  138.             
  139.             // 显示错误消息
  140.             lblMessage.Text = "保存产品时出错:" + ex.Message;
  141.             lblMessage.CssClass = "error";
  142.         }
  143.     }
  144.    
  145.     private void BindProductData(Product product)
  146.     {
  147.         hdnProductID.Value = product.ProductID.ToString();
  148.         txtProductName.Text = product.ProductName;
  149.         txtDescription.Text = product.Description;
  150.         txtUnitPrice.Text = product.UnitPrice.ToString();
  151.     }
  152. }
复制代码

最佳实践与性能优化

1. 优化视图状态使用

视图状态虽然方便,但会增加页面大小,影响性能。以下是一些优化技巧:
  1. // 示例:禁用不必要控件的视图状态
  2. protected void Page_Init(object sender, EventArgs e)
  3. {
  4.     // 禁用整个页面的视图状态
  5.     this.EnableViewState = false;
  6.    
  7.     // 或者只禁用特定控件的视图状态
  8.     lblMessage.EnableViewState = false;
  9.     gvUsers.EnableViewState = true; // 保留需要视图状态的控件
  10. }
  11. // 示例:使用会话状态替代视图状态存储大量数据
  12. protected void Page_Load(object sender, EventArgs e)
  13. {
  14.     if (!IsPostBack)
  15.     {
  16.         // 获取大量数据
  17.         DataTable largeDataSet = GetLargeDataSet();
  18.         
  19.         // 存储在会话状态而非视图状态中
  20.         Session["LargeDataSet"] = largeDataSet;
  21.         
  22.         // 绑定数据
  23.         gvLargeData.DataSource = largeDataSet;
  24.         gvLargeData.DataBind();
  25.     }
  26.     else
  27.     {
  28.         // 从会话状态恢复数据
  29.         DataTable largeDataSet = (DataTable)Session["LargeDataSet"];
  30.         gvLargeData.DataSource = largeDataSet;
  31.         gvLargeData.DataBind();
  32.     }
  33. }
复制代码

2. 实现数据缓存

缓存是提高Web应用程序性能的重要技术:
  1. // 示例:使用ASP.NET缓存
  2. public class ProductRepository
  3. {
  4.     public List<Product> GetAllProducts()
  5.     {
  6.         // 尝试从缓存获取数据
  7.         List<Product> products = HttpContext.Current.Cache["AllProducts"] as List<Product>;
  8.         
  9.         if (products == null)
  10.         {
  11.             // 缓存中没有数据,从数据库获取
  12.             using (var context = new NorthwindEntities())
  13.             {
  14.                 products = context.Products.ToList();
  15.                
  16.                 // 将数据存入缓存,设置过期时间为30分钟
  17.                 HttpContext.Current.Cache.Insert(
  18.                     "AllProducts",
  19.                     products,
  20.                     null,
  21.                     DateTime.Now.AddMinutes(30),
  22.                     System.Web.Caching.Cache.NoSlidingExpiration);
  23.             }
  24.         }
  25.         
  26.         return products;
  27.     }
  28.    
  29.     public void ClearProductsCache()
  30.     {
  31.         // 清除产品缓存
  32.         HttpContext.Current.Cache.Remove("AllProducts");
  33.     }
  34. }
  35. // 示例:使用SQL缓存依赖
  36. public class ProductRepositoryWithSqlDependency
  37. {
  38.     public List<Product> GetAllProducts()
  39.     {
  40.         // 尝试从缓存获取数据
  41.         List<Product> products = HttpContext.Current.Cache["AllProducts"] as List<Product>;
  42.         
  43.         if (products == null)
  44.         {
  45.             // 缓存中没有数据,从数据库获取
  46.             using (var context = new NorthwindEntities())
  47.             {
  48.                 products = context.Products.ToList();
  49.                
  50.                 // 创建SQL缓存依赖
  51.                 SqlCacheDependency dependency = new SqlCacheDependency("Northwind", "Products");
  52.                
  53.                 // 将数据存入缓存,并设置SQL缓存依赖
  54.                 HttpContext.Current.Cache.Insert(
  55.                     "AllProducts",
  56.                     products,
  57.                     dependency);
  58.             }
  59.         }
  60.         
  61.         return products;
  62.     }
  63. }
复制代码

在web.config中配置SQL缓存依赖:
  1. <system.web>
  2.   <caching>
  3.     <sqlCacheDependency enabled="true" pollTime="60000">
  4.       <databases>
  5.         <add name="Northwind" connectionStringName="NorthwindConnectionString" />
  6.       </databases>
  7.     </sqlCacheDependency>
  8.   </caching>
  9. </system.web>
复制代码

3. 使用输出缓存

输出缓存可以缓存整个页面或用户控件的输出:
  1. <%-- 示例:页面级输出缓存 --%>
  2. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ProductList.aspx.cs"
  3.     Inherits="WebApp.ProductList" %>
  4. <%@ OutputCache Duration="300" VaryByParam="page;category" %>
  5. <%-- 示例:用户控件级输出缓存 --%>
  6. <%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductList.ascx.cs"
  7.     Inherits="WebApp.Controls.ProductList" %>
  8. <%@ OutputCache Duration="300" VaryByControl="ddlCategory;ddlSortBy" %>
复制代码

4. 优化数据库访问

优化数据库访问可以显著提高应用程序性能:
  1. // 示例:使用存储过程优化数据库访问
  2. public class ProductRepository
  3. {
  4.     public List<Product> GetProductsByCategory(int categoryId)
  5.     {
  6.         List<Product> products = new List<Product>();
  7.         
  8.         using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString))
  9.         {
  10.             using (var command = new SqlCommand("GetProductsByCategory", connection))
  11.             {
  12.                 command.CommandType = CommandType.StoredProcedure;
  13.                 command.Parameters.AddWithValue("@CategoryID", categoryId);
  14.                
  15.                 connection.Open();
  16.                
  17.                 using (var reader = command.ExecuteReader())
  18.                 {
  19.                     while (reader.Read())
  20.                     {
  21.                         Product product = new Product();
  22.                         product.ProductID = (int)reader["ProductID"];
  23.                         product.ProductName = reader["ProductName"].ToString();
  24.                         product.UnitPrice = (decimal)reader["UnitPrice"];
  25.                         
  26.                         products.Add(product);
  27.                     }
  28.                 }
  29.             }
  30.         }
  31.         
  32.         return products;
  33.     }
  34.    
  35.     // 使用Entity Framework优化查询
  36.     public List<Product> GetTopSellingProducts(int count)
  37.     {
  38.         using (var context = new NorthwindEntities())
  39.         {
  40.             // 只选择需要的列,避免不必要的数据传输
  41.             var query = context.Products
  42.                 .Where(p => p.Discontinued == false)
  43.                 .OrderByDescending(p => p.OrderDetails.Sum(od => od.Quantity))
  44.                 .Take(count)
  45.                 .Select(p => new Product
  46.                 {
  47.                     ProductID = p.ProductID,
  48.                     ProductName = p.ProductName,
  49.                     UnitPrice = p.UnitPrice
  50.                 });
  51.                
  52.             return query.ToList();
  53.         }
  54.     }
  55. }
复制代码

5. 实现异步处理

异步处理可以提高应用程序的响应性和可伸缩性:
  1. // 示例:异步页面
  2. public partial class AsyncProductList : System.Web.UI.Page
  3. {
  4.     protected void Page_Load(object sender, EventArgs e)
  5.     {
  6.         // 注册异步任务
  7.         Page.RegisterAsyncTask(new PageAsyncTask(LoadProductsAsync));
  8.     }
  9.    
  10.     private async Task LoadProductsAsync()
  11.     {
  12.         try
  13.         {
  14.             // 异步获取产品数据
  15.             List<Product> products = await ProductRepository.GetAllProductsAsync();
  16.             
  17.             // 绑定到控件
  18.             gvProducts.DataSource = products;
  19.             gvProducts.DataBind();
  20.         }
  21.         catch (Exception ex)
  22.         {
  23.             Logger.Error("异步加载产品时出错", ex);
  24.             lblMessage.Text = "加载数据时出错:" + ex.Message;
  25.             lblMessage.CssClass = "error";
  26.         }
  27.     }
  28. }
  29. // 示例:异步数据访问
  30. public class ProductRepository
  31. {
  32.     public static async Task<List<Product>> GetAllProductsAsync()
  33.     {
  34.         using (var context = new NorthwindEntities())
  35.         {
  36.             // 使用Entity Framework的异步方法
  37.             return await context.Products.ToListAsync();
  38.         }
  39.     }
  40.    
  41.     public static async Task<Product> GetProductByIdAsync(int id)
  42.     {
  43.         using (var context = new NorthwindEntities())
  44.         {
  45.             return await context.Products.FindAsync(id);
  46.         }
  47.     }
  48.    
  49.     public static async Task<int> SaveProductAsync(Product product)
  50.     {
  51.         using (var context = new NorthwindEntities())
  52.         {
  53.             if (product.ProductID == 0)
  54.             {
  55.                 context.Products.Add(product);
  56.             }
  57.             else
  58.             {
  59.                 context.Entry(product).State = EntityState.Modified;
  60.             }
  61.             
  62.             return await context.SaveChangesAsync();
  63.         }
  64.     }
  65. }
复制代码

结论

ASP.NET Web Forms作为一个成熟稳定的Web开发框架,凭借其事件驱动编程模型、丰富的服务器控件和强大的数据绑定功能,仍然是构建企业级Web应用程序的可靠选择。通过本文介绍的强大功能、实际应用技巧和解决方案,开发者可以更好地利用Web Forms模板快速构建高效稳定的Web应用程序。

尽管Web Forms有其局限性,如页面生命周期复杂、视图状态开销大等,但通过采用适当的优化技术和最佳实践,如优化视图状态使用、实现缓存机制、优化数据库访问和采用异步处理等,可以显著提高应用程序的性能和可伸缩性。

随着.NET Core的兴起,虽然Web Forms不再是微软的主要关注点,但许多现有的Web Forms应用程序仍在运行和维护。对于这些应用程序,本文提供的技巧和解决方案可以帮助开发者解决常见挑战,提高应用程序的质量和性能。

最后,无论选择哪种技术栈,理解其核心概念、掌握最佳实践并不断学习新技术,都是成为一名优秀Web开发者的关键。希望本文能够帮助您更好地理解和应用ASP.NET Web Forms,构建出更加高效稳定的Web应用程序。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

频道订阅

频道订阅

加入社群

加入社群

联系我们|TG频道|RSS

Powered by Pixtech

© 2025 Pixtech Team.