活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Web Forms与AJAX整合实战从零开始学习如何在不放弃Web Forms便利性的同时引入AJAX提升用户体验

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-23 01:40:17 | 显示全部楼层 |阅读模式

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

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

x
引言

Web Forms作为ASP.NET框架的一部分,自2002年发布以来一直是Web开发的重要工具。它提供了事件驱动的编程模型、丰富的服务器控件和状态管理功能,使得开发人员可以快速构建复杂的Web应用程序。然而,传统的Web Forms应用依赖于完整的页面回发(postback),这会导致整个页面刷新,影响用户体验。

AJAX(Asynchronous JavaScript and XML)技术的出现改变了这一局面,它允许Web应用在不重新加载整个页面的情况下,与服务器进行异步通信并更新部分页面内容。将Web Forms与AJAX整合,可以兼顾Web Forms的开发便利性和AJAX的用户体验优势,是提升传统Web应用性能和用户体验的有效途径。

本文将从基础概念出发,逐步深入地介绍如何在Web Forms应用中引入AJAX技术,通过详细的代码示例和实践指导,帮助开发者在不放弃Web Forms便利性的同时,显著提升用户体验。

1. Web Forms基础回顾

1.1 Web Forms的工作原理

Web Forms是基于服务器端的Web应用框架,其核心思想是将Web开发抽象为类似Windows Forms的事件驱动模型。在Web Forms中:

• 页面生命周期包括初始化、加载视图状态、处理回发数据、加载页面、处理事件、呈现HTML等阶段
• 服务器控件在服务器端处理用户交互,并生成相应的HTML
• ViewState机制用于在页面回发之间保持控件状态

下面是一个简单的Web Forms页面示例:
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebFormsAjaxDemo._Default" %>
  2. <!DOCTYPE html>
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <title>Web Forms 示例</title>
  6. </head>
  7. <body>
  8.     <form id="form1" runat="server">
  9.         <div>
  10.             <asp:Label ID="lblMessage" runat="server" Text="请输入您的姓名:"></asp:Label>
  11.             <asp:TextBox ID="txtName" runat="server"></asp:TextBox>
  12.             <asp:Button ID="btnSubmit" runat="server" Text="提交" OnClick="btnSubmit_Click" />
  13.             <br />
  14.             <asp:Label ID="lblResult" runat="server"></asp:Label>
  15.         </div>
  16.     </form>
  17. </body>
  18. </html>
复制代码

对应的后台代码:
  1. using System;
  2. namespace WebFormsAjaxDemo
  3. {
  4.     public partial class _Default : System.Web.UI.Page
  5.     {
  6.         protected void Page_Load(object sender, EventArgs e)
  7.         {
  8.             // 页面首次加载时执行的代码
  9.         }
  10.         protected void btnSubmit_Click(object sender, EventArgs e)
  11.         {
  12.             // 按钮点击事件处理
  13.             if (!string.IsNullOrEmpty(txtName.Text))
  14.             {
  15.                 lblResult.Text = $"您好, {txtName.Text}! 欢迎使用Web Forms。";
  16.             }
  17.             else
  18.             {
  19.                 lblResult.Text = "请输入您的姓名。";
  20.             }
  21.         }
  22.     }
  23. }
复制代码

1.2 Web Forms的优势与局限

1. 快速开发:丰富的服务器控件和事件驱动模型大大提高了开发效率
2. 状态管理:ViewState和控件状态自动管理,简化了状态保持的复杂性
3. 熟悉度:对于有Windows Forms开发经验的程序员来说,学习曲线较平缓
4. 抽象层次:隐藏了HTTP和HTML的复杂性,开发者可以专注于业务逻辑

1. 页面回发:每次用户交互都导致整个页面刷新,影响用户体验
2. 页面大小:ViewState可能导致页面体积增大,影响加载速度
3. 控制力:对生成的HTML和JavaScript的控制有限
4. 测试困难:由于事件驱动和页面生命周期的复杂性,单元测试较为困难

2. AJAX基础概念

2.1 什么是AJAX

AJAX(Asynchronous JavaScript and XML)是一种创建交互式Web应用的技术组合,它允许Web应用在不重新加载整个页面的情况下,与服务器进行异步通信并更新部分页面内容。尽管名称中包含XML,但AJAX可以处理任何格式的数据,包括JSON、HTML和纯文本。

AJAX的核心技术包括:

• JavaScript:用于创建异步请求和处理响应
• XMLHttpRequest对象:用于在后台与服务器交换数据
• DOM(Document Object Model):用于动态更新页面内容
• CSS:用于样式化和动画效果

2.2 AJAX的优势

1. 提升用户体验:无需整页刷新,操作更加流畅
2. 减少带宽使用:只传输必要的数据,而不是整个页面
3. 提高响应速度:异步处理不会阻塞用户界面
4. 丰富的交互性:可以实现类似桌面应用的交互体验

2.3 基本的AJAX实现

下面是一个使用原生JavaScript实现AJAX请求的简单示例:
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4.     <title>原生AJAX示例</title>
  5.     <script type="text/javascript">
  6.         function loadContent() {
  7.             // 创建XMLHttpRequest对象
  8.             var xhr = new XMLHttpRequest();
  9.             
  10.             // 配置请求
  11.             xhr.open('GET', 'api/data', true);
  12.             
  13.             // 设置回调函数处理响应
  14.             xhr.onreadystatechange = function() {
  15.                 if (xhr.readyState === 4) { // 请求完成
  16.                     if (xhr.status === 200) { // 请求成功
  17.                         document.getElementById('result').innerHTML = xhr.responseText;
  18.                     } else {
  19.                         document.getElementById('result').innerHTML = 'Error: ' + xhr.status;
  20.                     }
  21.                 }
  22.             };
  23.             
  24.             // 发送请求
  25.             xhr.send();
  26.         }
  27.     </script>
  28. </head>
  29. <body>
  30.     <button onclick="loadContent()">加载数据</button>
  31.     <div id="result"></div>
  32. </body>
  33. </html>
复制代码

3. Web Forms与AJAX整合的必要性

3.1 传统Web Forms的痛点

在传统的Web Forms应用中,每次用户交互(如点击按钮、选择下拉列表等)都会触发一次完整的页面回发。这导致:

1. 页面闪烁:整个页面重新加载,用户会看到明显的闪烁
2. 滚动位置丢失:页面刷新后,用户需要重新滚动到之前的位置
3. 带宽浪费:即使只需要更新页面的一小部分,也要传输整个页面的HTML
4. 用户体验差:操作响应慢,无法提供流畅的交互体验

3.2 AJAX带来的改进

通过在Web Forms中引入AJAX,可以解决上述问题:

1. 部分页面更新:只更新需要变更的部分,而不是整个页面
2. 无闪烁体验:页面不会完全刷新,避免了闪烁现象
3. 保持状态:页面滚动位置和表单输入内容得以保留
4. 异步操作:用户可以在等待服务器响应的同时继续与页面交互

3.3 整合的价值

将Web Forms与AJAX整合,可以兼顾两者的优势:

• 保留Web Forms的快速开发能力和丰富的服务器控件
• 引入AJAX的流畅用户体验和高效数据传输
• 逐步改进现有Web Forms应用,无需完全重写
• 降低学习成本,对于熟悉Web Forms的开发者来说更容易上手

4. 在Web Forms中实现AJAX的方法

4.1 使用ASP.NET AJAX框架

ASP.NET AJAX框架(原代号为”Atlas”)是微软专门为ASP.NET设计的AJAX解决方案,它提供了与Web Forms深度集成的AJAX功能。

在Visual Studio中创建Web Forms项目时,ASP.NET AJAX通常已经包含在内。如果没有,可以通过NuGet包管理器安装:
  1. Install-Package Microsoft.AspNet.ScriptManager.WebForms
复制代码

ScriptManager是ASP.NET AJAX的核心控件,它负责管理客户端脚本库、注册Web服务和处理部分页面更新。每个使用AJAX功能的页面只能包含一个ScriptManager控件。
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AjaxDemo.aspx.cs" Inherits="WebFormsAjaxDemo.AjaxDemo" %>
  2. <!DOCTYPE html>
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <title>ASP.NET AJAX 示例</title>
  6. </head>
  7. <body>
  8.     <form id="form1" runat="server">
  9.         <!-- 必须的ScriptManager控件 -->
  10.         <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
  11.         
  12.         <div>
  13.             <h2>ASP.NET AJAX 演示</h2>
  14.             
  15.             <!-- 内容将在下面添加 -->
  16.         </div>
  17.     </form>
  18. </body>
  19. </html>
复制代码

UpdatePanel是ASP.NET AJAX中最常用的控件,它允许在不刷新整个页面的情况下更新页面的一部分。使用UpdatePanel,可以将现有的Web Forms控件包装起来,使它们支持部分页面更新。
  1. <asp:UpdatePanel ID="UpdatePanel1" runat="server">
  2.     <ContentTemplate>
  3.         <asp:Label ID="lblTime" runat="server" Text="点击按钮获取当前时间"></asp:Label>
  4.         <br />
  5.         <asp:Button ID="btnGetTime" runat="server" Text="获取时间" OnClick="btnGetTime_Click" />
  6.     </ContentTemplate>
  7. </asp:UpdatePanel>
复制代码

对应的后台代码:
  1. protected void btnGetTime_Click(object sender, EventArgs e)
  2. {
  3.     lblTime.Text = "当前时间是: " + DateTime.Now.ToString();
  4. }
复制代码

在这个例子中,当用户点击”获取时间”按钮时,只有UpdatePanel内的内容会被更新,而不是整个页面。

UpdateProgress控件用于在异步更新过程中显示加载状态,提升用户体验。
  1. <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
  2.     <ProgressTemplate>
  3.         <div class="loading">
  4.             <img src="Images/loading.gif" alt="加载中..." />
  5.             <p>正在处理,请稍候...</p>
  6.         </div>
  7.     </ProgressTemplate>
  8. </asp:UpdateProgress>
复制代码

对应的CSS样式:
  1. .loading {
  2.     position: fixed;
  3.     top: 0;
  4.     left: 0;
  5.     width: 100%;
  6.     height: 100%;
  7.     background-color: rgba(255, 255, 255, 0.7);
  8.     display: flex;
  9.     flex-direction: column;
  10.     justify-content: center;
  11.     align-items: center;
  12.     z-index: 1000;
  13. }
  14. .loading img {
  15.     width: 50px;
  16.     height: 50px;
  17. }
  18. .loading p {
  19.     margin-top: 10px;
  20.     font-size: 16px;
  21.     color: #333;
  22. }
复制代码

Timer控件允许在指定的时间间隔内触发部分页面更新,适用于需要定期刷新数据的场景。
  1. <asp:UpdatePanel ID="UpdatePanel2" runat="server">
  2.     <ContentTemplate>
  3.         <asp:Label ID="lblServerTime" runat="server"></asp:Label>
  4.         <asp:Timer ID="Timer1" runat="server" Interval="5000" OnTick="Timer1_Tick"></asp:Timer>
  5.     </ContentTemplate>
  6. </asp:UpdatePanel>
复制代码

对应的后台代码:
  1. protected void Timer1_Tick(object sender, EventArgs e)
  2. {
  3.     lblServerTime.Text = "服务器时间: " + DateTime.Now.ToString();
  4. }
  5. protected void Page_Load(object sender, EventArgs e)
  6. {
  7.     if (!IsPostBack)
  8.     {
  9.         lblServerTime.Text = "服务器时间: " + DateTime.Now.ToString();
  10.     }
  11. }
复制代码

4.2 使用jQuery AJAX

虽然ASP.NET AJAX框架提供了与Web Forms深度集成的解决方案,但许多开发者更喜欢使用jQuery这样的JavaScript库来实现AJAX功能,因为它更灵活、更轻量,并且有更广泛的社区支持。

首先,需要在页面中引入jQuery库。可以通过CDN或本地文件引入:
  1. <head runat="server">
  2.     <title>jQuery AJAX 示例</title>
  3.     <!-- 通过CDN引入jQuery -->
  4.     <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  5. </head>
复制代码

在Web Forms的后台代码中,可以创建静态的WebMethod方法,这些方法可以通过AJAX直接调用。
  1. using System.Web.Services;
  2. public partial class jQueryAjaxDemo : System.Web.UI.Page
  3. {
  4.     protected void Page_Load(object sender, EventArgs e)
  5.     {
  6.         // 页面加载代码
  7.     }
  8.     [WebMethod]
  9.     public static string GetServerTime()
  10.     {
  11.         return DateTime.Now.ToString();
  12.     }
  13.     [WebMethod]
  14.     public static string CalculateSum(int num1, int num2)
  15.     {
  16.         return (num1 + num2).ToString();
  17.     }
  18.     [WebMethod]
  19.     public static object GetProductInfo(int productId)
  20.     {
  21.         // 模拟从数据库获取产品信息
  22.         var products = new[]
  23.         {
  24.             new { Id = 1, Name = "笔记本电脑", Price = 5999.00m, Stock = 50 },
  25.             new { Id = 2, Name = "智能手机", Price = 3999.00m, Stock = 100 },
  26.             new { Id = 3, Name = "平板电脑", Price = 2999.00m, Stock = 30 }
  27.         };
  28.         var product = products.FirstOrDefault(p => p.Id == productId);
  29.         
  30.         if (product == null)
  31.         {
  32.             return new { Success = false, Message = "产品不存在" };
  33.         }
  34.         
  35.         return new { Success = true, Data = product };
  36.     }
  37. }
复制代码

在前端页面中,可以使用jQuery的AJAX方法调用后台的WebMethod:
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="jQueryAjaxDemo.aspx.cs" Inherits="WebFormsAjaxDemo.jQueryAjaxDemo" %>
  2. <!DOCTYPE html>
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <title>jQuery AJAX 示例</title>
  6.     <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  7.     <style>
  8.         body {
  9.             font-family: Arial, sans-serif;
  10.             max-width: 800px;
  11.             margin: 0 auto;
  12.             padding: 20px;
  13.         }
  14.         .section {
  15.             margin-bottom: 30px;
  16.             padding: 15px;
  17.             border: 1px solid #ddd;
  18.             border-radius: 5px;
  19.         }
  20.         .result {
  21.             margin-top: 10px;
  22.             padding: 10px;
  23.             background-color: #f5f5f5;
  24.             border-radius: 3px;
  25.         }
  26.         .loading {
  27.             display: none;
  28.             color: #666;
  29.         }
  30.         button {
  31.             padding: 8px 15px;
  32.             background-color: #4CAF50;
  33.             color: white;
  34.             border: none;
  35.             border-radius: 3px;
  36.             cursor: pointer;
  37.         }
  38.         button:hover {
  39.             background-color: #45a049;
  40.         }
  41.         input[type="text"], input[type="number"] {
  42.             padding: 8px;
  43.             width: 100px;
  44.             border: 1px solid #ddd;
  45.             border-radius: 3px;
  46.         }
  47.         select {
  48.             padding: 8px;
  49.             border: 1px solid #ddd;
  50.             border-radius: 3px;
  51.         }
  52.     </style>
  53. </head>
  54. <body>
  55.     <form id="form1" runat="server">
  56.         <h1>jQuery AJAX 与 Web Forms 整合示例</h1>
  57.         
  58.         <!-- 获取服务器时间示例 -->
  59.         <div class="section">
  60.             <h2>获取服务器时间</h2>
  61.             <button id="btnGetTime">获取时间</button>
  62.             <div class="loading" id="loadingTime">加载中...</div>
  63.             <div class="result" id="resultTime"></div>
  64.         </div>
  65.         
  66.         <!-- 计算两数之和示例 -->
  67.         <div class="section">
  68.             <h2>计算两数之和</h2>
  69.             <input type="number" id="num1" placeholder="第一个数" />
  70.             <span>+</span>
  71.             <input type="number" id="num2" placeholder="第二个数" />
  72.             <button id="btnCalculate">计算</button>
  73.             <div class="loading" id="loadingCalculate">计算中...</div>
  74.             <div class="result" id="resultCalculate"></div>
  75.         </div>
  76.         
  77.         <!-- 获取产品信息示例 -->
  78.         <div class="section">
  79.             <h2>获取产品信息</h2>
  80.             <select id="productSelect">
  81.                 <option value="1">笔记本电脑</option>
  82.                 <option value="2">智能手机</option>
  83.                 <option value="3">平板电脑</option>
  84.             </select>
  85.             <button id="btnGetProduct">获取产品信息</button>
  86.             <div class="loading" id="loadingProduct">加载中...</div>
  87.             <div class="result" id="resultProduct"></div>
  88.         </div>
  89.     </form>
  90.     <script>
  91.         $(document).ready(function() {
  92.             // 获取服务器时间
  93.             $("#btnGetTime").click(function() {
  94.                 $("#loadingTime").show();
  95.                 $("#resultTime").html("");
  96.                
  97.                 $.ajax({
  98.                     type: "POST",
  99.                     url: "jQueryAjaxDemo.aspx/GetServerTime",
  100.                     contentType: "application/json; charset=utf-8",
  101.                     dataType: "json",
  102.                     success: function(response) {
  103.                         $("#resultTime").html("服务器时间: " + response.d);
  104.                         $("#loadingTime").hide();
  105.                     },
  106.                     error: function(xhr, status, error) {
  107.                         $("#resultTime").html("错误: " + error);
  108.                         $("#loadingTime").hide();
  109.                     }
  110.                 });
  111.             });
  112.             
  113.             // 计算两数之和
  114.             $("#btnCalculate").click(function() {
  115.                 var num1 = $("#num1").val();
  116.                 var num2 = $("#num2").val();
  117.                
  118.                 if (num1 === "" || num2 === "") {
  119.                     $("#resultCalculate").html("请输入两个数字");
  120.                     return;
  121.                 }
  122.                
  123.                 $("#loadingCalculate").show();
  124.                 $("#resultCalculate").html("");
  125.                
  126.                 $.ajax({
  127.                     type: "POST",
  128.                     url: "jQueryAjaxDemo.aspx/CalculateSum",
  129.                     data: JSON.stringify({ num1: parseInt(num1), num2: parseInt(num2) }),
  130.                     contentType: "application/json; charset=utf-8",
  131.                     dataType: "json",
  132.                     success: function(response) {
  133.                         $("#resultCalculate").html("计算结果: " + num1 + " + " + num2 + " = " + response.d);
  134.                         $("#loadingCalculate").hide();
  135.                     },
  136.                     error: function(xhr, status, error) {
  137.                         $("#resultCalculate").html("错误: " + error);
  138.                         $("#loadingCalculate").hide();
  139.                     }
  140.                 });
  141.             });
  142.             
  143.             // 获取产品信息
  144.             $("#btnGetProduct").click(function() {
  145.                 var productId = $("#productSelect").val();
  146.                
  147.                 $("#loadingProduct").show();
  148.                 $("#resultProduct").html("");
  149.                
  150.                 $.ajax({
  151.                     type: "POST",
  152.                     url: "jQueryAjaxDemo.aspx/GetProductInfo",
  153.                     data: JSON.stringify({ productId: parseInt(productId) }),
  154.                     contentType: "application/json; charset=utf-8",
  155.                     dataType: "json",
  156.                     success: function(response) {
  157.                         var result = response.d;
  158.                         if (result.Success) {
  159.                             var product = result.Data;
  160.                             var productInfo = "<strong>" + product.Name + "</strong><br>";
  161.                             productInfo += "价格: ¥" + product.Price.toFixed(2) + "<br>";
  162.                             productInfo += "库存: " + product.Stock + " 件";
  163.                             $("#resultProduct").html(productInfo);
  164.                         } else {
  165.                             $("#resultProduct").html(result.Message);
  166.                         }
  167.                         $("#loadingProduct").hide();
  168.                     },
  169.                     error: function(xhr, status, error) {
  170.                         $("#resultProduct").html("错误: " + error);
  171.                         $("#loadingProduct").hide();
  172.                     }
  173.                 });
  174.             });
  175.         });
  176.     </script>
  177. </body>
  178. </html>
复制代码

4.3 使用PageMethods

PageMethods是ASP.NET AJAX提供的一种简化方式,允许直接从客户端JavaScript调用服务器端方法,无需创建Web服务。

首先,需要在ScriptManager控件中启用PageMethods:
  1. <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
  2. </asp:ScriptManager>
复制代码

在后台代码中,创建公共静态方法并标记为[WebMethod]:
  1. using System.Web.Services;
  2. public partial class PageMethodsDemo : System.Web.UI.Page
  3. {
  4.     protected void Page_Load(object sender, EventArgs e)
  5.     {
  6.         // 页面加载代码
  7.     }
  8.     [WebMethod]
  9.     public static string GetGreeting(string name)
  10.     {
  11.         return $"你好, {name}! 欢迎使用PageMethods。";
  12.     }
  13.     [WebMethod]
  14.     public static string GetCurrentWeather(string city)
  15.     {
  16.         // 模拟天气数据
  17.         var weatherData = new System.Collections.Generic.Dictionary<string, string>
  18.         {
  19.             { "北京", "晴天, 25°C" },
  20.             { "上海", "多云, 28°C" },
  21.             { "广州", "小雨, 30°C" },
  22.             { "深圳", "晴天, 32°C" }
  23.         };
  24.         if (weatherData.ContainsKey(city))
  25.         {
  26.             return $"{city}的天气: {weatherData[city]}";
  27.         }
  28.         else
  29.         {
  30.             return $"抱歉, 没有找到{city}的天气信息。";
  31.         }
  32.     }
  33. }
复制代码

在前端页面中,可以使用PageMethods对象调用服务器端方法:
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="PageMethodsDemo.aspx.cs" Inherits="WebFormsAjaxDemo.PageMethodsDemo" %>
  2. <!DOCTYPE html>
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <title>PageMethods 示例</title>
  6.     <style>
  7.         body {
  8.             font-family: Arial, sans-serif;
  9.             max-width: 800px;
  10.             margin: 0 auto;
  11.             padding: 20px;
  12.         }
  13.         .section {
  14.             margin-bottom: 30px;
  15.             padding: 15px;
  16.             border: 1px solid #ddd;
  17.             border-radius: 5px;
  18.         }
  19.         .result {
  20.             margin-top: 10px;
  21.             padding: 10px;
  22.             background-color: #f5f5f5;
  23.             border-radius: 3px;
  24.         }
  25.         .loading {
  26.             display: none;
  27.             color: #666;
  28.         }
  29.         button {
  30.             padding: 8px 15px;
  31.             background-color: #4CAF50;
  32.             color: white;
  33.             border: none;
  34.             border-radius: 3px;
  35.             cursor: pointer;
  36.         }
  37.         button:hover {
  38.             background-color: #45a049;
  39.         }
  40.         input[type="text"] {
  41.             padding: 8px;
  42.             width: 200px;
  43.             border: 1px solid #ddd;
  44.             border-radius: 3px;
  45.         }
  46.         select {
  47.             padding: 8px;
  48.             border: 1px solid #ddd;
  49.             border-radius: 3px;
  50.         }
  51.     </style>
  52. </head>
  53. <body>
  54.     <form id="form1" runat="server">
  55.         <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
  56.         </asp:ScriptManager>
  57.         
  58.         <h1>PageMethods 与 Web Forms 整合示例</h1>
  59.         
  60.         <!-- 获取问候语示例 -->
  61.         <div class="section">
  62.             <h2>获取问候语</h2>
  63.             <input type="text" id="txtName" placeholder="请输入您的姓名" />
  64.             <button id="btnGetGreeting">获取问候语</button>
  65.             <div class="loading" id="loadingGreeting">处理中...</div>
  66.             <div class="result" id="resultGreeting"></div>
  67.         </div>
  68.         
  69.         <!-- 获取天气信息示例 -->
  70.         <div class="section">
  71.             <h2>获取天气信息</h2>
  72.             <select id="citySelect">
  73.                 <option value="北京">北京</option>
  74.                 <option value="上海">上海</option>
  75.                 <option value="广州">广州</option>
  76.                 <option value="深圳">深圳</option>
  77.             </select>
  78.             <button id="btnGetWeather">获取天气</button>
  79.             <div class="loading" id="loadingWeather">查询中...</div>
  80.             <div class="result" id="resultWeather"></div>
  81.         </div>
  82.     </form>
  83.     <script>
  84.         // 获取问候语
  85.         document.getElementById("btnGetGreeting").onclick = function() {
  86.             var name = document.getElementById("txtName").value;
  87.             
  88.             if (name === "") {
  89.                 document.getElementById("resultGreeting").innerHTML = "请输入您的姓名";
  90.                 return;
  91.             }
  92.             
  93.             document.getElementById("loadingGreeting").style.display = "block";
  94.             document.getElementById("resultGreeting").innerHTML = "";
  95.             
  96.             // 调用PageMethod
  97.             PageMethods.GetGreeting(name, function(result) {
  98.                 document.getElementById("resultGreeting").innerHTML = result;
  99.                 document.getElementById("loadingGreeting").style.display = "none";
  100.             }, function(error) {
  101.                 document.getElementById("resultGreeting").innerHTML = "错误: " + error.get_message();
  102.                 document.getElementById("loadingGreeting").style.display = "none";
  103.             });
  104.         };
  105.         
  106.         // 获取天气信息
  107.         document.getElementById("btnGetWeather").onclick = function() {
  108.             var city = document.getElementById("citySelect").value;
  109.             
  110.             document.getElementById("loadingWeather").style.display = "block";
  111.             document.getElementById("resultWeather").innerHTML = "";
  112.             
  113.             // 调用PageMethod
  114.             PageMethods.GetCurrentWeather(city, function(result) {
  115.                 document.getElementById("resultWeather").innerHTML = result;
  116.                 document.getElementById("loadingWeather").style.display = "none";
  117.             }, function(error) {
  118.                 document.getElementById("resultWeather").innerHTML = "错误: " + error.get_message();
  119.                 document.getElementById("loadingWeather").style.display = "none";
  120.             });
  121.         };
  122.     </script>
  123. </body>
  124. </html>
复制代码

5. 实战案例:构建一个AJAX增强的Web Forms应用

5.1 项目概述

让我们构建一个简单的产品管理应用,该应用将展示如何在不放弃Web Forms便利性的同时,通过AJAX技术提升用户体验。这个应用将包括以下功能:

1. 产品列表展示
2. 产品搜索
3. 产品详情查看
4. 产品添加和编辑

5.2 创建项目

在Visual Studio中创建一个新的ASP.NET Web Forms应用:

1. 打开Visual Studio
2. 选择”文件” > “新建” > “项目”
3. 选择”ASP.NET Web 应用程序”
4. 命名为”ProductManagement”
5. 选择”Web Forms”模板并创建项目

5.3 设计数据模型

首先,创建一个简单的产品模型类:
  1. // Models/Product.cs
  2. using System;
  3. namespace ProductManagement.Models
  4. {
  5.     public class Product
  6.     {
  7.         public int Id { get; set; }
  8.         public string Name { get; set; }
  9.         public string Description { get; set; }
  10.         public decimal Price { get; set; }
  11.         public int Stock { get; set; }
  12.         public string Category { get; set; }
  13.         public DateTime CreatedDate { get; set; }
  14.     }
  15. }
复制代码

5.4 创建数据访问层

创建一个简单的产品数据访问类,用于模拟数据库操作:
  1. // DAL/ProductRepository.cs
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using ProductManagement.Models;
  6. namespace ProductManagement.DAL
  7. {
  8.     public class ProductRepository
  9.     {
  10.         private static List<Product> _products;
  11.         private static int _nextId = 1;
  12.         static ProductRepository()
  13.         {
  14.             // 初始化一些示例产品
  15.             _products = new List<Product>
  16.             {
  17.                 new Product
  18.                 {
  19.                     Id = _nextId++,
  20.                     Name = "笔记本电脑",
  21.                     Description = "高性能笔记本电脑,适合办公和娱乐",
  22.                     Price = 5999.00m,
  23.                     Stock = 50,
  24.                     Category = "电脑",
  25.                     CreatedDate = DateTime.Now.AddDays(-30)
  26.                 },
  27.                 new Product
  28.                 {
  29.                     Id = _nextId++,
  30.                     Name = "智能手机",
  31.                     Description = "最新款智能手机,拍照效果极佳",
  32.                     Price = 3999.00m,
  33.                     Stock = 100,
  34.                     Category = "手机",
  35.                     CreatedDate = DateTime.Now.AddDays(-20)
  36.                 },
  37.                 new Product
  38.                 {
  39.                     Id = _nextId++,
  40.                     Name = "平板电脑",
  41.                     Description = "轻薄便携的平板电脑,阅读和娱乐的理想选择",
  42.                     Price = 2999.00m,
  43.                     Stock = 30,
  44.                     Category = "电脑",
  45.                     CreatedDate = DateTime.Now.AddDays(-15)
  46.                 },
  47.                 new Product
  48.                 {
  49.                     Id = _nextId++,
  50.                     Name = "无线耳机",
  51.                     Description = "高品质无线蓝牙耳机,降噪效果出色",
  52.                     Price = 899.00m,
  53.                     Stock = 200,
  54.                     Category = "配件",
  55.                     CreatedDate = DateTime.Now.AddDays(-10)
  56.                 },
  57.                 new Product
  58.                 {
  59.                     Id = _nextId++,
  60.                     Name = "智能手表",
  61.                     Description = "多功能智能手表,健康监测和运动追踪",
  62.                     Price = 1299.00m,
  63.                     Stock = 75,
  64.                     Category = "配件",
  65.                     CreatedDate = DateTime.Now.AddDays(-5)
  66.                 }
  67.             };
  68.         }
  69.         public List<Product> GetAllProducts()
  70.         {
  71.             return _products.OrderBy(p => p.Id).ToList();
  72.         }
  73.         public Product GetProductById(int id)
  74.         {
  75.             return _products.FirstOrDefault(p => p.Id == id);
  76.         }
  77.         public List<Product> SearchProducts(string keyword, string category)
  78.         {
  79.             var query = _products.AsQueryable();
  80.             if (!string.IsNullOrEmpty(keyword))
  81.             {
  82.                 query = query.Where(p =>
  83.                     p.Name.Contains(keyword) ||
  84.                     p.Description.Contains(keyword));
  85.             }
  86.             if (!string.IsNullOrEmpty(category) && category != "全部")
  87.             {
  88.                 query = query.Where(p => p.Category == category);
  89.             }
  90.             return query.OrderBy(p => p.Id).ToList();
  91.         }
  92.         public Product AddProduct(Product product)
  93.         {
  94.             product.Id = _nextId++;
  95.             product.CreatedDate = DateTime.Now;
  96.             _products.Add(product);
  97.             return product;
  98.         }
  99.         public bool UpdateProduct(Product product)
  100.         {
  101.             var existingProduct = _products.FirstOrDefault(p => p.Id == product.Id);
  102.             if (existingProduct != null)
  103.             {
  104.                 existingProduct.Name = product.Name;
  105.                 existingProduct.Description = product.Description;
  106.                 existingProduct.Price = product.Price;
  107.                 existingProduct.Stock = product.Stock;
  108.                 existingProduct.Category = product.Category;
  109.                 return true;
  110.             }
  111.             return false;
  112.         }
  113.         public bool DeleteProduct(int id)
  114.         {
  115.             var product = _products.FirstOrDefault(p => p.Id == id);
  116.             if (product != null)
  117.             {
  118.                 _products.Remove(product);
  119.                 return true;
  120.             }
  121.             return false;
  122.         }
  123.         public List<string> GetCategories()
  124.         {
  125.             return _products.Select(p => p.Category).Distinct().OrderBy(c => c).ToList();
  126.         }
  127.     }
  128. }
复制代码

5.5 创建产品列表页面

创建一个产品列表页面,使用AJAX技术实现搜索和分页功能:
  1. <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ProductList.aspx.cs" Inherits="ProductManagement.ProductList" %>
  2. <!DOCTYPE html>
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <head runat="server">
  5.     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  6.     <title>产品列表</title>
  7.     <link href="Content/bootstrap.min.css" rel="stylesheet" />
  8.     <link href="Content/Site.css" rel="stylesheet" />
  9.     <style>
  10.         .product-card {
  11.             transition: transform 0.3s;
  12.             height: 100%;
  13.         }
  14.         .product-card:hover {
  15.             transform: translateY(-5px);
  16.             box-shadow: 0 4px 8px rgba(0,0,0,0.1);
  17.         }
  18.         .product-image {
  19.             height: 200px;
  20.             object-fit: cover;
  21.         }
  22.         .product-price {
  23.             color: #e44d26;
  24.             font-weight: bold;
  25.             font-size: 1.2em;
  26.         }
  27.         .search-panel {
  28.             background-color: #f8f9fa;
  29.             padding: 15px;
  30.             border-radius: 5px;
  31.             margin-bottom: 20px;
  32.         }
  33.         .loading {
  34.             text-align: center;
  35.             padding: 20px;
  36.         }
  37.         .no-products {
  38.             text-align: center;
  39.             padding: 40px;
  40.             color: #6c757d;
  41.         }
  42.         .pagination {
  43.             justify-content: center;
  44.         }
  45.         .product-actions {
  46.             display: flex;
  47.             justify-content: space-between;
  48.             margin-top: 10px;
  49.         }
  50.     </style>
  51. </head>
  52. <body>
  53.     <form id="form1" runat="server">
  54.         <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
  55.             <Services>
  56.                 <asp:ServiceReference Path="Services/ProductService.asmx" />
  57.             </Services>
  58.         </asp:ScriptManager>
  59.         <div class="container">
  60.             <h1 class="my-4">产品列表</h1>
  61.             
  62.             <!-- 搜索面板 -->
  63.             <div class="search-panel">
  64.                 <div class="row">
  65.                     <div class="col-md-5">
  66.                         <div class="form-group">
  67.                             <label for="txtKeyword">关键词</label>
  68.                             <asp:TextBox ID="txtKeyword" runat="server" CssClass="form-control" placeholder="输入产品名称或描述"></asp:TextBox>
  69.                         </div>
  70.                     </div>
  71.                     <div class="col-md-4">
  72.                         <div class="form-group">
  73.                             <label for="ddlCategory">分类</label>
  74.                             <asp:DropDownList ID="ddlCategory" runat="server" CssClass="form-control">
  75.                                 <asp:ListItem Text="全部" Value=""></asp:ListItem>
  76.                             </asp:DropDownList>
  77.                         </div>
  78.                     </div>
  79.                     <div class="col-md-3">
  80.                         <div class="form-group">
  81.                             <label> </label>
  82.                             <div>
  83.                                 <asp:Button ID="btnSearch" runat="server" Text="搜索" CssClass="btn btn-primary btn-block" OnClientClick="searchProducts(); return false;" />
  84.                             </div>
  85.                         </div>
  86.                     </div>
  87.                 </div>
  88.             </div>
  89.             
  90.             <!-- 产品列表 -->
  91.             <div class="row" id="productList">
  92.                 <!-- 产品将通过AJAX动态加载 -->
  93.             </div>
  94.             
  95.             <!-- 加载中提示 -->
  96.             <div class="loading" id="loadingIndicator" style="display: none;">
  97.                 <div class="spinner-border text-primary" role="status">
  98.                     <span class="sr-only">加载中...</span>
  99.                 </div>
  100.                 <p>正在加载产品数据...</p>
  101.             </div>
  102.             
  103.             <!-- 无产品提示 -->
  104.             <div class="no-products" id="noProductsMessage" style="display: none;">
  105.                 <h3>没有找到匹配的产品</h3>
  106.                 <p>请尝试使用其他关键词或分类进行搜索</p>
  107.             </div>
  108.             
  109.             <!-- 分页控件 -->
  110.             <nav aria-label="Page navigation" id="paginationContainer" style="display: none;">
  111.                 <ul class="pagination" id="pagination">
  112.                     <!-- 分页按钮将通过JavaScript动态生成 -->
  113.                 </ul>
  114.             </nav>
  115.             
  116.             <!-- 添加产品按钮 -->
  117.             <div class="text-center mt-4">
  118.                 <button type="button" class="btn btn-success" data-toggle="modal" data-target="#addProductModal">
  119.                     <i class="fas fa-plus"></i> 添加新产品
  120.                 </button>
  121.             </div>
  122.         </div>
  123.         
  124.         <!-- 产品详情模态框 -->
  125.         <div class="modal fade" id="productDetailModal" tabindex="-1" role="dialog" aria-labelledby="productDetailModalLabel" aria-hidden="true">
  126.             <div class="modal-dialog modal-lg" role="document">
  127.                 <div class="modal-content">
  128.                     <div class="modal-header">
  129.                         <h5 class="modal-title" id="productDetailModalLabel">产品详情</h5>
  130.                         <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  131.                             <span aria-hidden="true">&times;</span>
  132.                         </button>
  133.                     </div>
  134.                     <div class="modal-body" id="productDetailContent">
  135.                         <!-- 产品详情将通过AJAX动态加载 -->
  136.                     </div>
  137.                     <div class="modal-footer">
  138.                         <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
  139.                         <button type="button" class="btn btn-primary" id="btnEditProduct">编辑产品</button>
  140.                     </div>
  141.                 </div>
  142.             </div>
  143.         </div>
  144.         
  145.         <!-- 添加产品模态框 -->
  146.         <div class="modal fade" id="addProductModal" tabindex="-1" role="dialog" aria-labelledby="addProductModalLabel" aria-hidden="true">
  147.             <div class="modal-dialog" role="document">
  148.                 <div class="modal-content">
  149.                     <div class="modal-header">
  150.                         <h5 class="modal-title" id="addProductModalLabel">添加新产品</h5>
  151.                         <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  152.                             <span aria-hidden="true">&times;</span>
  153.                         </button>
  154.                     </div>
  155.                     <div class="modal-body">
  156.                         <div class="form-group">
  157.                             <label for="txtAddName">产品名称</label>
  158.                             <input type="text" class="form-control" id="txtAddName" />
  159.                         </div>
  160.                         <div class="form-group">
  161.                             <label for="txtAddDescription">产品描述</label>
  162.                             <textarea class="form-control" id="txtAddDescription" rows="3"></textarea>
  163.                         </div>
  164.                         <div class="form-group">
  165.                             <label for="txtAddPrice">价格</label>
  166.                             <input type="number" class="form-control" id="txtAddPrice" step="0.01" />
  167.                         </div>
  168.                         <div class="form-group">
  169.                             <label for="txtAddStock">库存</label>
  170.                             <input type="number" class="form-control" id="txtAddStock" />
  171.                         </div>
  172.                         <div class="form-group">
  173.                             <label for="txtAddCategory">分类</label>
  174.                             <input type="text" class="form-control" id="txtAddCategory" />
  175.                         </div>
  176.                     </div>
  177.                     <div class="modal-footer">
  178.                         <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
  179.                         <button type="button" class="btn btn-primary" id="btnSaveProduct">保存</button>
  180.                     </div>
  181.                 </div>
  182.             </div>
  183.         </div>
  184.         
  185.         <!-- 编辑产品模态框 -->
  186.         <div class="modal fade" id="editProductModal" tabindex="-1" role="dialog" aria-labelledby="editProductModalLabel" aria-hidden="true">
  187.             <div class="modal-dialog" role="document">
  188.                 <div class="modal-content">
  189.                     <div class="modal-header">
  190.                         <h5 class="modal-title" id="editProductModalLabel">编辑产品</h5>
  191.                         <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  192.                             <span aria-hidden="true">&times;</span>
  193.                         </button>
  194.                     </div>
  195.                     <div class="modal-body">
  196.                         <input type="hidden" id="txtEditId" />
  197.                         <div class="form-group">
  198.                             <label for="txtEditName">产品名称</label>
  199.                             <input type="text" class="form-control" id="txtEditName" />
  200.                         </div>
  201.                         <div class="form-group">
  202.                             <label for="txtEditDescription">产品描述</label>
  203.                             <textarea class="form-control" id="txtEditDescription" rows="3"></textarea>
  204.                         </div>
  205.                         <div class="form-group">
  206.                             <label for="txtEditPrice">价格</label>
  207.                             <input type="number" class="form-control" id="txtEditPrice" step="0.01" />
  208.                         </div>
  209.                         <div class="form-group">
  210.                             <label for="txtEditStock">库存</label>
  211.                             <input type="number" class="form-control" id="txtEditStock" />
  212.                         </div>
  213.                         <div class="form-group">
  214.                             <label for="txtEditCategory">分类</label>
  215.                             <input type="text" class="form-control" id="txtEditCategory" />
  216.                         </div>
  217.                     </div>
  218.                     <div class="modal-footer">
  219.                         <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
  220.                         <button type="button" class="btn btn-danger" id="btnDeleteProduct">删除</button>
  221.                         <button type="button" class="btn btn-primary" id="btnUpdateProduct">更新</button>
  222.                     </div>
  223.                 </div>
  224.             </div>
  225.         </div>
  226.     </form>
  227.     <script src="Scripts/jquery-3.6.0.min.js"></script>
  228.     <script src="Scripts/bootstrap.bundle.min.js"></script>
  229.     <script src="Scripts/fontawesome-all.min.js"></script>
  230.     <script>
  231.         // 全局变量
  232.         var currentPage = 1;
  233.         var pageSize = 6;
  234.         var totalPages = 0;
  235.         var currentKeyword = "";
  236.         var currentCategory = "";
  237.         var currentProductId = 0;
  238.         // 页面加载完成后执行
  239.         $(document).ready(function() {
  240.             // 加载分类
  241.             loadCategories();
  242.             
  243.             // 加载产品列表
  244.             searchProducts();
  245.             
  246.             // 绑定保存产品按钮事件
  247.             $("#btnSaveProduct").click(function() {
  248.                 saveProduct();
  249.             });
  250.             
  251.             // 绑定更新产品按钮事件
  252.             $("#btnUpdateProduct").click(function() {
  253.                 updateProduct();
  254.             });
  255.             
  256.             // 绑定删除产品按钮事件
  257.             $("#btnDeleteProduct").click(function() {
  258.                 if (confirm("确定要删除这个产品吗?")) {
  259.                     deleteProduct();
  260.                 }
  261.             });
  262.             
  263.             // 绑定编辑产品按钮事件
  264.             $("#btnEditProduct").click(function() {
  265.                 // 关闭详情模态框
  266.                 $("#productDetailModal").modal("hide");
  267.                
  268.                 // 打开编辑模态框
  269.                 $("#editProductModal").modal("show");
  270.             });
  271.         });
  272.         // 加载分类
  273.         function loadCategories() {
  274.             $.ajax({
  275.                 type: "POST",
  276.                 url: "Services/ProductService.asmx/GetCategories",
  277.                 contentType: "application/json; charset=utf-8",
  278.                 dataType: "json",
  279.                 success: function(response) {
  280.                     var categories = response.d;
  281.                     var ddlCategory = $("#ddlCategory");
  282.                     
  283.                     // 清空现有选项(除了"全部")
  284.                     ddlCategory.find("option:not(:first)").remove();
  285.                     
  286.                     // 添加分类选项
  287.                     for (var i = 0; i < categories.length; i++) {
  288.                         ddlCategory.append($("<option></option>").val(categories[i]).text(categories[i]));
  289.                     }
  290.                 },
  291.                 error: function(xhr, status, error) {
  292.                     console.error("加载分类失败: " + error);
  293.                 }
  294.             });
  295.         }
  296.         // 搜索产品
  297.         function searchProducts(page) {
  298.             // 显示加载指示器
  299.             $("#loadingIndicator").show();
  300.             $("#productList").hide();
  301.             $("#noProductsMessage").hide();
  302.             $("#paginationContainer").hide();
  303.             
  304.             // 设置当前页码
  305.             if (page) {
  306.                 currentPage = page;
  307.             } else {
  308.                 currentPage = 1;
  309.             }
  310.             
  311.             // 获取搜索条件
  312.             currentKeyword = $("#txtKeyword").val();
  313.             currentCategory = $("#ddlCategory").val();
  314.             
  315.             // 调用Web服务搜索产品
  316.             $.ajax({
  317.                 type: "POST",
  318.                 url: "Services/ProductService.asmx/SearchProducts",
  319.                 data: JSON.stringify({
  320.                     keyword: currentKeyword,
  321.                     category: currentCategory,
  322.                     page: currentPage,
  323.                     pageSize: pageSize
  324.                 }),
  325.                 contentType: "application/json; charset=utf-8",
  326.                 dataType: "json",
  327.                 success: function(response) {
  328.                     var result = response.d;
  329.                     
  330.                     // 隐藏加载指示器
  331.                     $("#loadingIndicator").hide();
  332.                     
  333.                     if (result.Products.length > 0) {
  334.                         // 显示产品列表
  335.                         displayProducts(result.Products);
  336.                         $("#productList").show();
  337.                         
  338.                         // 更新分页信息
  339.                         totalPages = result.TotalPages;
  340.                         updatePagination(currentPage, totalPages);
  341.                         $("#paginationContainer").show();
  342.                     } else {
  343.                         // 显示无产品消息
  344.                         $("#noProductsMessage").show();
  345.                     }
  346.                 },
  347.                 error: function(xhr, status, error) {
  348.                     // 隐藏加载指示器
  349.                     $("#loadingIndicator").hide();
  350.                     
  351.                     // 显示错误消息
  352.                     $("#productList").html("<div class='alert alert-danger'>加载产品失败: " + error + "</div>");
  353.                     $("#productList").show();
  354.                 }
  355.             });
  356.         }
  357.         // 显示产品列表
  358.         function displayProducts(products) {
  359.             var productList = $("#productList");
  360.             productList.empty();
  361.             
  362.             for (var i = 0; i < products.length; i++) {
  363.                 var product = products[i];
  364.                 var productCard = $("<div class='col-md-4 mb-4'></div>");
  365.                
  366.                 var cardContent = "<div class='card product-card'>" +
  367.                     "<img src='https://picsum.photos/seed/product" + product.Id + "/400/200.jpg' class='card-img-top product-image' alt='" + product.Name + "'>" +
  368.                     "<div class='card-body'>" +
  369.                     "<h5 class='card-title'>" + product.Name + "</h5>" +
  370.                     "<p class='card-text'>" + product.Description.substring(0, 50) + "...</p>" +
  371.                     "<p class='product-price'>¥" + product.Price.toFixed(2) + "</p>" +
  372.                     "<div class='product-actions'>" +
  373.                     "<button class='btn btn-sm btn-outline-primary' onclick='showProductDetail(" + product.Id + ")'>详情</button>" +
  374.                     "<span class='text-muted'>库存: " + product.Stock + "</span>" +
  375.                     "</div>" +
  376.                     "</div>" +
  377.                     "</div>";
  378.                
  379.                 productCard.append(cardContent);
  380.                 productList.append(productCard);
  381.             }
  382.         }
  383.         // 更新分页控件
  384.         function updatePagination(currentPage, totalPages) {
  385.             var pagination = $("#pagination");
  386.             pagination.empty();
  387.             
  388.             // 上一页按钮
  389.             var prevDisabled = currentPage === 1 ? "disabled" : "";
  390.             pagination.append("<li class='page-item " + prevDisabled + "'><a class='page-link' href='#' onclick='searchProducts(" + (currentPage - 1) + "); return false;'>上一页</a></li>");
  391.             
  392.             // 页码按钮
  393.             var startPage = Math.max(1, currentPage - 2);
  394.             var endPage = Math.min(totalPages, startPage + 4);
  395.             
  396.             if (endPage - startPage < 4) {
  397.                 startPage = Math.max(1, endPage - 4);
  398.             }
  399.             
  400.             if (startPage > 1) {
  401.                 pagination.append("<li class='page-item'><a class='page-link' href='#' onclick='searchProducts(1); return false;'>1</a></li>");
  402.                 if (startPage > 2) {
  403.                     pagination.append("<li class='page-item disabled'><a class='page-link' href='#'>...</a></li>");
  404.                 }
  405.             }
  406.             
  407.             for (var i = startPage; i <= endPage; i++) {
  408.                 var active = i === currentPage ? "active" : "";
  409.                 pagination.append("<li class='page-item " + active + "'><a class='page-link' href='#' onclick='searchProducts(" + i + "); return false;'>" + i + "</a></li>");
  410.             }
  411.             
  412.             if (endPage < totalPages) {
  413.                 if (endPage < totalPages - 1) {
  414.                     pagination.append("<li class='page-item disabled'><a class='page-link' href='#'>...</a></li>");
  415.                 }
  416.                 pagination.append("<li class='page-item'><a class='page-link' href='#' onclick='searchProducts(" + totalPages + "); return false;'>" + totalPages + "</a></li>");
  417.             }
  418.             
  419.             // 下一页按钮
  420.             var nextDisabled = currentPage === totalPages ? "disabled" : "";
  421.             pagination.append("<li class='page-item " + nextDisabled + "'><a class='page-link' href='#' onclick='searchProducts(" + (currentPage + 1) + "); return false;'>下一页</a></li>");
  422.         }
  423.         // 显示产品详情
  424.         function showProductDetail(productId) {
  425.             currentProductId = productId;
  426.             
  427.             // 调用Web服务获取产品详情
  428.             $.ajax({
  429.                 type: "POST",
  430.                 url: "Services/ProductService.asmx/GetProductById",
  431.                 data: JSON.stringify({ id: productId }),
  432.                 contentType: "application/json; charset=utf-8",
  433.                 dataType: "json",
  434.                 success: function(response) {
  435.                     var product = response.d;
  436.                     
  437.                     var detailContent = "<div class='row'>" +
  438.                         "<div class='col-md-6'>" +
  439.                         "<img src='https://picsum.photos/seed/product" + product.Id + "/600/400.jpg' class='img-fluid' alt='" + product.Name + "'>" +
  440.                         "</div>" +
  441.                         "<div class='col-md-6'>" +
  442.                         "<h3>" + product.Name + "</h3>" +
  443.                         "<p class='text-muted'>分类: " + product.Category + "</p>" +
  444.                         "<p class='product-price'>¥" + product.Price.toFixed(2) + "</p>" +
  445.                         "<p><strong>库存:</strong> " + product.Stock + " 件</p>" +
  446.                         "<p><strong>添加日期:</strong> " + new Date(product.CreatedDate).toLocaleDateString() + "</p>" +
  447.                         "<h5>产品描述</h5>" +
  448.                         "<p>" + product.Description + "</p>" +
  449.                         "</div>" +
  450.                         "</div>";
  451.                     
  452.                     $("#productDetailContent").html(detailContent);
  453.                     $("#productDetailModal").modal("show");
  454.                 },
  455.                 error: function(xhr, status, error) {
  456.                     $("#productDetailContent").html("<div class='alert alert-danger'>加载产品详情失败: " + error + "</div>");
  457.                     $("#productDetailModal").modal("show");
  458.                 }
  459.             });
  460.         }
  461.         // 保存新产品
  462.         function saveProduct() {
  463.             var product = {
  464.                 Name: $("#txtAddName").val(),
  465.                 Description: $("#txtAddDescription").val(),
  466.                 Price: parseFloat($("#txtAddPrice").val()),
  467.                 Stock: parseInt($("#txtAddStock").val()),
  468.                 Category: $("#txtAddCategory").val()
  469.             };
  470.             
  471.             // 验证输入
  472.             if (!product.Name || !product.Description || isNaN(product.Price) || isNaN(product.Stock) || !product.Category) {
  473.                 alert("请填写所有必填字段");
  474.                 return;
  475.             }
  476.             
  477.             // 调用Web服务保存产品
  478.             $.ajax({
  479.                 type: "POST",
  480.                 url: "Services/ProductService.asmx/AddProduct",
  481.                 data: JSON.stringify({ product: product }),
  482.                 contentType: "application/json; charset=utf-8",
  483.                 dataType: "json",
  484.                 success: function(response) {
  485.                     // 关闭模态框
  486.                     $("#addProductModal").modal("hide");
  487.                     
  488.                     // 清空表单
  489.                     $("#txtAddName").val("");
  490.                     $("#txtAddDescription").val("");
  491.                     $("#txtAddPrice").val("");
  492.                     $("#txtAddStock").val("");
  493.                     $("#txtAddCategory").val("");
  494.                     
  495.                     // 刷新产品列表
  496.                     searchProducts();
  497.                     
  498.                     // 显示成功消息
  499.                     alert("产品添加成功");
  500.                 },
  501.                 error: function(xhr, status, error) {
  502.                     alert("添加产品失败: " + error);
  503.                 }
  504.             });
  505.         }
  506.         // 更新产品
  507.         function updateProduct() {
  508.             var product = {
  509.                 Id: parseInt($("#txtEditId").val()),
  510.                 Name: $("#txtEditName").val(),
  511.                 Description: $("#txtEditDescription").val(),
  512.                 Price: parseFloat($("#txtEditPrice").val()),
  513.                 Stock: parseInt($("#txtEditStock").val()),
  514.                 Category: $("#txtEditCategory").val()
  515.             };
  516.             
  517.             // 验证输入
  518.             if (!product.Name || !product.Description || isNaN(product.Price) || isNaN(product.Stock) || !product.Category) {
  519.                 alert("请填写所有必填字段");
  520.                 return;
  521.             }
  522.             
  523.             // 调用Web服务更新产品
  524.             $.ajax({
  525.                 type: "POST",
  526.                 url: "Services/ProductService.asmx/UpdateProduct",
  527.                 data: JSON.stringify({ product: product }),
  528.                 contentType: "application/json; charset=utf-8",
  529.                 dataType: "json",
  530.                 success: function(response) {
  531.                     var result = response.d;
  532.                     
  533.                     if (result.Success) {
  534.                         // 关闭模态框
  535.                         $("#editProductModal").modal("hide");
  536.                         
  537.                         // 刷新产品列表
  538.                         searchProducts();
  539.                         
  540.                         // 显示成功消息
  541.                         alert("产品更新成功");
  542.                     } else {
  543.                         alert("更新产品失败: " + result.Message);
  544.                     }
  545.                 },
  546.                 error: function(xhr, status, error) {
  547.                     alert("更新产品失败: " + error);
  548.                 }
  549.             });
  550.         }
  551.         // 删除产品
  552.         function deleteProduct() {
  553.             // 调用Web服务删除产品
  554.             $.ajax({
  555.                 type: "POST",
  556.                 url: "Services/ProductService.asmx/DeleteProduct",
  557.                 data: JSON.stringify({ id: currentProductId }),
  558.                 contentType: "application/json; charset=utf-8",
  559.                 dataType: "json",
  560.                 success: function(response) {
  561.                     var result = response.d;
  562.                     
  563.                     if (result.Success) {
  564.                         // 关闭模态框
  565.                         $("#editProductModal").modal("hide");
  566.                         
  567.                         // 刷新产品列表
  568.                         searchProducts();
  569.                         
  570.                         // 显示成功消息
  571.                         alert("产品删除成功");
  572.                     } else {
  573.                         alert("删除产品失败: " + result.Message);
  574.                     }
  575.                 },
  576.                 error: function(xhr, status, error) {
  577.                     alert("删除产品失败: " + error);
  578.                 }
  579.             });
  580.         }
  581.         // 当产品详情模态框关闭时,重置当前产品ID
  582.         $("#productDetailModal").on("hidden.bs.modal", function() {
  583.             currentProductId = 0;
  584.         });
  585.         // 当编辑产品模态框打开时,填充表单数据
  586.         $("#productDetailModal").on("hidden.bs.modal", function() {
  587.             if (currentProductId > 0) {
  588.                 // 调用Web服务获取产品详情
  589.                 $.ajax({
  590.                     type: "POST",
  591.                     url: "Services/ProductService.asmx/GetProductById",
  592.                     data: JSON.stringify({ id: currentProductId }),
  593.                     contentType: "application/json; charset=utf-8",
  594.                     dataType: "json",
  595.                     success: function(response) {
  596.                         var product = response.d;
  597.                         
  598.                         $("#txtEditId").val(product.Id);
  599.                         $("#txtEditName").val(product.Name);
  600.                         $("#txtEditDescription").val(product.Description);
  601.                         $("#txtEditPrice").val(product.Price);
  602.                         $("#txtEditStock").val(product.Stock);
  603.                         $("#txtEditCategory").val(product.Category);
  604.                     },
  605.                     error: function(xhr, status, error) {
  606.                         alert("加载产品详情失败: " + error);
  607.                     }
  608.                 });
  609.             }
  610.         });
  611.     </script>
  612. </body>
  613. </html>
复制代码

5.6 创建Web服务

创建一个ASP.NET Web服务,用于处理AJAX请求:
  1. // Services/ProductService.asmx.cs
  2. using System;
  3. using System.Web.Services;
  4. using System.Collections.Generic;
  5. using ProductManagement.Models;
  6. using ProductManagement.DAL;
  7. namespace ProductManagement.Services
  8. {
  9.     /// <summary>
  10.     /// ProductService 的摘要说明
  11.     /// </summary>
  12.     [WebService(Namespace = "http://tempuri.org/")]
  13.     [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  14.     [System.ComponentModel.ToolboxItem(false)]
  15.     // 若要允许使用 ASP.NET AJAX 从脚本中调用此 Web 服务,请取消注释以下行。
  16.     [System.Web.Script.Services.ScriptService]
  17.     public class ProductService : System.Web.Services.WebService
  18.     {
  19.         private ProductRepository _repository = new ProductRepository();
  20.         [WebMethod]
  21.         public List<Product> GetAllProducts()
  22.         {
  23.             return _repository.GetAllProducts();
  24.         }
  25.         [WebMethod]
  26.         public Product GetProductById(int id)
  27.         {
  28.             return _repository.GetProductById(id);
  29.         }
  30.         [WebMethod]
  31.         public SearchResult SearchProducts(string keyword, string category, int page, int pageSize)
  32.         {
  33.             var products = _repository.SearchProducts(keyword, category);
  34.             var totalCount = products.Count;
  35.             var totalPages = (int)Math.Ceiling((double)totalCount / pageSize);
  36.             
  37.             var pagedProducts = products
  38.                 .Skip((page - 1) * pageSize)
  39.                 .Take(pageSize)
  40.                 .ToList();
  41.             
  42.             return new SearchResult
  43.             {
  44.                 Products = pagedProducts,
  45.                 TotalCount = totalCount,
  46.                 TotalPages = totalPages,
  47.                 CurrentPage = page,
  48.                 PageSize = pageSize
  49.             };
  50.         }
  51.         [WebMethod]
  52.         public Product AddProduct(Product product)
  53.         {
  54.             return _repository.AddProduct(product);
  55.         }
  56.         [WebMethod]
  57.         public OperationResult UpdateProduct(Product product)
  58.         {
  59.             bool success = _repository.UpdateProduct(product);
  60.             return new OperationResult
  61.             {
  62.                 Success = success,
  63.                 Message = success ? "产品更新成功" : "产品更新失败"
  64.             };
  65.         }
  66.         [WebMethod]
  67.         public OperationResult DeleteProduct(int id)
  68.         {
  69.             bool success = _repository.DeleteProduct(id);
  70.             return new OperationResult
  71.             {
  72.                 Success = success,
  73.                 Message = success ? "产品删除成功" : "产品删除失败"
  74.             };
  75.         }
  76.         [WebMethod]
  77.         public List<string> GetCategories()
  78.         {
  79.             return _repository.GetCategories();
  80.         }
  81.     }
  82.     public class SearchResult
  83.     {
  84.         public List<Product> Products { get; set; }
  85.         public int TotalCount { get; set; }
  86.         public int TotalPages { get; set; }
  87.         public int CurrentPage { get; set; }
  88.         public int PageSize { get; set; }
  89.     }
  90.     public class OperationResult
  91.     {
  92.         public bool Success { get; set; }
  93.         public string Message { get; set; }
  94.     }
  95. }
复制代码

5.7 实现AJAX功能

在上面的产品列表页面中,我们已经实现了以下AJAX功能:

1. 产品搜索:用户可以输入关键词和选择分类进行搜索,搜索结果通过AJAX异步加载,无需整页刷新。
2. 分页功能:产品列表支持分页,点击分页按钮时,只更新产品列表部分,不刷新整个页面。
3. 产品详情:点击产品卡片的”详情”按钮,通过AJAX加载产品详细信息并在模态框中显示。
4. 产品管理:支持添加、编辑和删除产品,所有操作都通过AJAX异步完成,操作完成后自动刷新产品列表。

产品搜索:用户可以输入关键词和选择分类进行搜索,搜索结果通过AJAX异步加载,无需整页刷新。

分页功能:产品列表支持分页,点击分页按钮时,只更新产品列表部分,不刷新整个页面。

产品详情:点击产品卡片的”详情”按钮,通过AJAX加载产品详细信息并在模态框中显示。

产品管理:支持添加、编辑和删除产品,所有操作都通过AJAX异步完成,操作完成后自动刷新产品列表。

5.8 用户体验优化

为了进一步提升用户体验,我们还可以添加以下优化:

1. 加载指示器:在AJAX请求期间显示加载动画,让用户知道系统正在处理他们的请求。
2. 表单验证:在客户端添加表单验证,减少不必要的服务器请求。
3. 错误处理:添加友好的错误提示,帮助用户理解发生了什么问题。
4. 动画效果:添加平滑的过渡动画,使界面变化更加自然。
5. 响应式设计:确保应用在不同设备上都能良好显示。

加载指示器:在AJAX请求期间显示加载动画,让用户知道系统正在处理他们的请求。

表单验证:在客户端添加表单验证,减少不必要的服务器请求。

错误处理:添加友好的错误提示,帮助用户理解发生了什么问题。

动画效果:添加平滑的过渡动画,使界面变化更加自然。

响应式设计:确保应用在不同设备上都能良好显示。

6. 最佳实践和注意事项

6.1 性能优化

1. 减少页面大小:禁用不必要的ViewState使用ScriptManager的ScriptMode=“Release”属性压缩和合并JavaScript和CSS文件
2. 禁用不必要的ViewState
3. 使用ScriptManager的ScriptMode=“Release”属性
4. 压缩和合并JavaScript和CSS文件

• 禁用不必要的ViewState
• 使用ScriptManager的ScriptMode=“Release”属性
• 压缩和合并JavaScript和CSS文件
  1. <asp:ScriptManager ID="ScriptManager1" runat="server" ScriptMode="Release">
  2.     <CompositeScript>
  3.         <Scripts>
  4.             <asp:ScriptReference Path="Scripts/jquery-3.6.0.min.js" />
  5.             <asp:ScriptReference Path="Scripts/bootstrap.min.js" />
  6.         </Scripts>
  7.     </CompositeScript>
  8. </asp:ScriptManager>
复制代码

1. 优化AJAX请求:减少AJAX请求的数据量使用GET请求获取数据,POST请求提交数据实现请求缓存和防抖动
2. 减少AJAX请求的数据量
3. 使用GET请求获取数据,POST请求提交数据
4. 实现请求缓存和防抖动

• 减少AJAX请求的数据量
• 使用GET请求获取数据,POST请求提交数据
• 实现请求缓存和防抖动
  1. // 防抖动函数
  2. function debounce(func, wait) {
  3.     let timeout;
  4.     return function() {
  5.         const context = this;
  6.         const args = arguments;
  7.         clearTimeout(timeout);
  8.         timeout = setTimeout(() => {
  9.             func.apply(context, args);
  10.         }, wait);
  11.     };
  12. }
  13. // 使用防抖动优化搜索输入
  14. $("#txtKeyword").on("input", debounce(function() {
  15.     searchProducts();
  16. }, 300));
复制代码

1. 服务器端优化:实现数据缓存使用分页减少数据传输量优化数据库查询
2. 实现数据缓存
3. 使用分页减少数据传输量
4. 优化数据库查询

• 实现数据缓存
• 使用分页减少数据传输量
• 优化数据库查询
  1. // 使用缓存优化分类数据
  2. [WebMethod]
  3. public List<string> GetCategories()
  4. {
  5.     string cacheKey = "ProductCategories";
  6.    
  7.     if (HttpContext.Current.Cache[cacheKey] == null)
  8.     {
  9.         var categories = _repository.GetCategories();
  10.         HttpContext.Current.Cache.Insert(cacheKey, categories, null,
  11.             DateTime.Now.AddHours(1), System.Web.Caching.Cache.NoSlidingExpiration);
  12.         return categories;
  13.     }
  14.    
  15.     return (List<string>)HttpContext.Current.Cache[cacheKey];
  16. }
复制代码

6.2 安全考虑

1. 防止跨站脚本攻击(XSS):对用户输入进行验证和编码使用ASP.NET的请求验证功能
2. 对用户输入进行验证和编码
3. 使用ASP.NET的请求验证功能

• 对用户输入进行验证和编码
• 使用ASP.NET的请求验证功能
  1. // 在Web.config中启用请求验证
  2. <configuration>
  3.   <system.web>
  4.     <pages validateRequest="true" />
  5.     <httpRuntime requestValidationMode="4.5" />
  6.   </system.web>
  7. </configuration>
复制代码

1. 防止跨站请求伪造(CSRF):使用ASP.NET的AntiForgeryToken实现请求来源验证
2. 使用ASP.NET的AntiForgeryToken
3. 实现请求来源验证

• 使用ASP.NET的AntiForgeryToken
• 实现请求来源验证
  1. // 在页面中添加AntiForgeryToken
  2. <%@ Register Assembly="System.Web.Helpers, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.Helpers" TagPrefix="asp" %>
  3. <asp:AntiForgeryToken ID="antiForgeryToken" runat="server" />
复制代码
  1. // 在AJAX请求中包含AntiForgeryToken
  2. function getAntiForgeryToken() {
  3.     var token = $("input[name='__RequestVerificationToken']").val();
  4.     return { __RequestVerificationToken: token };
  5. }
  6. $.ajax({
  7.     type: "POST",
  8.     url: "Services/ProductService.asmx/AddProduct",
  9.     data: JSON.stringify({
  10.         __RequestVerificationToken: getAntiForgeryToken().__RequestVerificationToken,
  11.         product: product
  12.     }),
  13.     contentType: "application/json; charset=utf-8",
  14.     dataType: "json",
  15.     success: function(response) {
  16.         // 处理响应
  17.     }
  18. });
复制代码

1. 防止SQL注入:使用参数化查询使用ORM框架
2. 使用参数化查询
3. 使用ORM框架

• 使用参数化查询
• 使用ORM框架
  1. // 使用参数化查询
  2. public List<Product> SearchProducts(string keyword, string category)
  3. {
  4.     var query = "SELECT * FROM Products WHERE 1=1";
  5.     var parameters = new List<SqlParameter>();
  6.    
  7.     if (!string.IsNullOrEmpty(keyword))
  8.     {
  9.         query += " AND (Name LIKE @keyword OR Description LIKE @keyword)";
  10.         parameters.Add(new SqlParameter("@keyword", "%" + keyword + "%"));
  11.     }
  12.    
  13.     if (!string.IsNullOrEmpty(category))
  14.     {
  15.         query += " AND Category = @category";
  16.         parameters.Add(new SqlParameter("@category", category));
  17.     }
  18.    
  19.     // 执行查询并返回结果
  20.     // ...
  21. }
复制代码

6.3 可维护性考虑

1. 代码组织:分离关注点(UI、业务逻辑、数据访问)使用一致的命名约定添加适当的注释
2. 分离关注点(UI、业务逻辑、数据访问)
3. 使用一致的命名约定
4. 添加适当的注释
5. 错误处理:实现全局错误处理记录错误日志提供友好的错误消息
6. 实现全局错误处理
7. 记录错误日志
8. 提供友好的错误消息

代码组织:

• 分离关注点(UI、业务逻辑、数据访问)
• 使用一致的命名约定
• 添加适当的注释

错误处理:

• 实现全局错误处理
• 记录错误日志
• 提供友好的错误消息
  1. // 全局AJAX错误处理
  2. $(document).ajaxError(function(event, jqXHR, settings, thrownError) {
  3.     console.error("AJAX错误:", thrownError);
  4.    
  5.     // 显示友好的错误消息
  6.     if (jqXHR.status === 404) {
  7.         alert("请求的资源不存在");
  8.     } else if (jqXHR.status === 500) {
  9.         alert("服务器内部错误,请稍后再试");
  10.     } else {
  11.         alert("发生错误: " + thrownError);
  12.     }
  13. });
复制代码

1. 测试:编写单元测试进行集成测试执行用户界面测试
2. 编写单元测试
3. 进行集成测试
4. 执行用户界面测试

• 编写单元测试
• 进行集成测试
• 执行用户界面测试
  1. // 使用NUnit进行单元测试
  2. [TestFixture]
  3. public class ProductRepositoryTests
  4. {
  5.     private ProductRepository _repository;
  6.    
  7.     [SetUp]
  8.     public void Setup()
  9.     {
  10.         _repository = new ProductRepository();
  11.     }
  12.    
  13.     [Test]
  14.     public void GetAllProducts_ShouldReturnAllProducts()
  15.     {
  16.         // Arrange
  17.         // Act
  18.         var products = _repository.GetAllProducts();
  19.         
  20.         // Assert
  21.         Assert.IsNotNull(products);
  22.         Assert.IsTrue(products.Count > 0);
  23.     }
  24.    
  25.     [Test]
  26.     public void GetProductById_WithValidId_ShouldReturnProduct()
  27.     {
  28.         // Arrange
  29.         int validId = 1;
  30.         
  31.         // Act
  32.         var product = _repository.GetProductById(validId);
  33.         
  34.         // Assert
  35.         Assert.IsNotNull(product);
  36.         Assert.AreEqual(validId, product.Id);
  37.     }
  38. }
复制代码

7. 总结

本文详细介绍了如何在Web Forms应用中整合AJAX技术,以提升用户体验而不放弃Web Forms的便利性。我们从基础概念出发,逐步深入到实战案例,展示了多种实现AJAX的方法,包括使用ASP.NET AJAX框架、jQuery AJAX和PageMethods。

通过本文的学习,你应该能够:

1. 理解Web Forms和AJAX的基本概念和工作原理
2. 掌握在Web Forms中实现AJAX的多种方法
3. 能够构建一个完整的AJAX增强的Web Forms应用
4. 了解性能优化、安全考虑和可维护性方面的最佳实践

Web Forms与AJAX的整合不是一种非此即彼的选择,而是一种互补的策略。通过合理地使用AJAX技术,我们可以在保留Web Forms开发便利性的同时,显著提升用户体验,使传统Web应用焕发新的活力。

随着Web技术的不断发展,Web Forms可能不再是最新、最热门的技术,但对于许多现有应用和企业环境来说,它仍然是一个可靠、高效的选择。通过引入AJAX,我们可以延长这些应用的生命周期,为用户提供更好的体验,同时也为开发者提供更广阔的技术视野。

希望本文能够帮助你更好地理解和应用Web Forms与AJAX的整合技术,为你的项目带来实质性的改进。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则