首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 利用 AJAX (PageMethods) 取得動態建立表格中文字方塊的值:主從表單無刷新提交完整範例

     編輯:ASP.NET 基礎

前言

典型需求:一個頁面同時編輯 **主表** (公司基本資料) 與 **從表** (多筆部門資料),部門列數動態增減。傳統 PostBack 會導致動態控制項狀態遺失,本文示範使用 **JavaScript 動態建立表列** + **ASP.NET AJAX PageMethods** 將資料打包傳至後端 `WebMethod`,實現無刷新提交與伺服器端集中處理。

完整前端代碼

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="DynamicTableDemo.aspx.cs" Inherits="Demo.DynamicTableDemo" %>
<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head runat="server">
    <title>動態主從表單 AJAX 提交示範</title>
    <style>
        body { font-family: Microsoft JhengHei; margin: 20px; }
        table { border-collapse: collapse; width: 100%; margin-bottom: 15px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background: #f2f2f2; }
        .btn { padding: 6px 12px; margin-right: 5px; cursor: pointer; }
        .message { margin-top: 10px; padding: 10px; border-radius: 4px; }
        .success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
        .error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true" />
        
        <h2>公司與部門資料維護</h2>
        
        <!-- 主表區塊 -->
        <fieldset>
            <legend>公司基本資料</legend>
            <table>
                <tr>
                    <td>公司名稱:</td>
                    <td><input type="text" id="txtCompanyName" style="width:300px" /></td>
                    <td>公司類型:</td>
                    <td>
                        <select id="ddlCompanyType">
                            <option value="1">國營企業</option>
                            <option value="2">民營企業</option>
                            <option value="3">外資企業</option>
                        </select>
                    </td>
                </tr>
                <tr>
                    <td>公司規模:</td>
                    <td><input type="text" id="txtCompanySize" style="width:300px" /></td>
                </tr>
            </table>
        </fieldset>
        
        <!-- 從表區塊 (動態列) -->
        <fieldset>
            <legend>部門資料 
                <button type="button" class="btn" onclick="addRow()">+ 新增部門</button>
            </legend>
            <table id="tblDepartments">
                <thead>
                    <tr>
                        <th style="width:30%">部門名稱</th>
                        <th style="width:30%">經理</th>
                        <th style="width:30%">電話</th>
                        <th style="width:10%">操作</th>
                    </tr>
                </thead>
                <tbody>
                    <!-- 預設一列 -->
                    <tr data-row="1">
                        <td><input type="text" class="dept-name" placeholder="部門名稱" /></td>
                        <td><input type="text" class="dept-manager" placeholder="經理" /></td>
                        <td><input type="text" class="dept-phone" placeholder="電話" /></td>
                        <td><button type="button" class="btn" onclick="delRow(this)">刪除</button></td>
                    </tr>
                </tbody>
            </table>
        </fieldset>
        
        <button type="button" class="btn" onclick="submitData()" style="background:#007bff;color:white;border:none;padding:10px 20px;font-size:16px;">提交儲存</button>
        <div id="resultMsg" class="message" style="display:none;"></div>
    </form>

    <script>
        let rowIndex = 1; // 全域列計數器

        // 新增列
        function addRow() {
            rowIndex++;
            const tbody = document.querySelector('#tblDepartments tbody');
            const tr = document.createElement('tr');
            tr.dataset.row = rowIndex;
            tr.innerHTML = `
                <td><input type="text" class="dept-name" placeholder="部門名稱" /></td>
                <td><input type="text" class="dept-manager" placeholder="經理" /></td>
                <td><input type="text" class="dept-phone" placeholder="電話" /></td>
                <td><button type="button" class="btn" onclick="delRow(this)">刪除</button></td>
            `;
            tbody.appendChild(tr);
        }

        // 刪除列
        function delRow(btn) {
            const rows = document.querySelectorAll('#tblDepartments tbody tr');
            if (rows.length <= 1) { alert('至少保留一列'); return; }
            btn.closest('tr').remove();
            // 可選:重新編號 data-row
        }

        // 收集資料並呼叫 PageMethod
        function submitData() {
            // 1. 主表資料
            const companyName = document.getElementById('txtCompanyName').value.trim();
            const companySize = document.getElementById('txtCompanySize').value.trim();
            const companyType = document.getElementById('ddlCompanyType').value;
            
            if (!companyName) { alert('請輸入公司名稱'); return; }

            // 2. 從表資料
            const deptRows = document.querySelectorAll('#tblDepartments tbody tr');
            const departments = [];
            
            deptRows.forEach(tr => {
                const name = tr.querySelector('.dept-name').value.trim();
                const manager = tr.querySelector('.dept-manager').value.trim();
                const phone = tr.querySelector('.dept-phone').value.trim();
                if (name || manager || phone) { // 至少有一欄有值才送出
                    departments.push({ Name: name, Manager: manager, Phone: phone });
                }
            });
            
            if (departments.length === 0) { alert('請至少填寫一筆部門資料'); return; }

            // 3. 呼叫 PageMethod (非同步)
            showMessage('資料送出中...', 'info');
            PageMethods.SaveCompanyData(companyName, companySize, companyType, departments, onSuccess, onFailure);
        }

        // 成功回呼
        function onSuccess(result) {
            showMessage(result.Message, result.Success ? 'success' : 'error');
            if (result.Success) {
                // 可選:清空表單、重導向、更新 UI
                console.log('伺服器回傳 ID:', result.Data);
            }
        }

        // 失敗回呼
        function onFailure(error) {
            showMessage('AJAX 呼叫失敗:' + error.get_message(), 'error');
        }

        // 顯示訊息
        function showMessage(text, type) {
            const div = document.getElementById('resultMsg');
            div.textContent = text;
            div.className = 'message ' + type;
            div.style.display = 'block';
        }
    </script>
</body>
</html>

後端代碼

using System;
using System.Collections.Generic;
using System.Web.Services;
using System.Web.Script.Services;

public partial class DynamicTableDemo : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e) { }

    // 必須加上 [WebMethod] 且為 static
    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public static AjaxResult SaveCompanyData(
        string companyName, 
        string companySize, 
        string companyType, 
        List departments)
    {
        try
        {
            // 1. 驗證 (實務應更完整)
            if (string.IsNullOrWhiteSpace(companyName))
                return new AjaxResult { Success = false, Message = "公司名稱不可為空" };

            // 2. 建立主表實體
            var company = new CompanyEntity
            {
                Name = companyName,
                Size = companySize,
                Type = companyType,
                CreateTime = DateTime.Now
            };

            // 3. 建立從表實體集合
            var deptEntities = new List();
            foreach (var dto in departments)
            {
                deptEntities.Add(new DepartmentEntity
                {
                    Name = dto.Name,
                    Manager = dto.Manager,
                    Phone = dto.Phone,
                    Company = company // 導航屬性
                });
            }

            // 4. 存入資料庫 (此處用假資料模擬,實務用 EF / ADO.NET / Dapper)
            int newCompanyId = MockDatabase.Save(company, deptEntities);

            return new AjaxResult 
            { 
                Success = true, 
                Message = $"儲存成功!公司 ID: {newCompanyId},部門筆數: {deptEntities.Count}", 
                Data = newCompanyId 
            };
        }
        catch (Exception ex)
        {
            // 記錄日誌
            return new AjaxResult { Success = false, Message = "伺服器錯誤:" + ex.Message };
        }
    }
}

// --- DTO / 實體類別 ---
public class DepartmentDto
{
    public string Name { get; set; }
    public string Manager { get; set; }
    public string Phone { get; set; }
}

public class CompanyEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Size { get; set; }
    public string Type { get; set; }
    public DateTime CreateTime { get; set; }
}

public class DepartmentEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Manager { get; set; }
    public string Phone { get; set; }
    public CompanyEntity Company { get; set; }
}

public class AjaxResult
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public object Data { get; set; }
}

// --- 模擬資料庫 ---
public static class MockDatabase
{
    private static int _seq = 1000;
    public static int Save(CompanyEntity company, List depts)
    {
        company.Id = ++_seq;
        foreach (var d in depts) d.Id = ++_seq;
        // 實際寫入 DB 邏輯...
        System.Diagnostics.Debug.WriteLine($"Saved Company: {company.Name}, Depts: {depts.Count}");
        return company.Id;
    }
}

關鍵技術點解析

技術點說明
ScriptManager.EnablePageMethods="true"啟用 PageMethods 功能,自動產生 JS Proxy 類別 (`PageMethods.MethodName`)。
[WebMethod] + static只有 `static` 方法且標記 `[WebMethod]` 才能被 PageMethods 呼叫。
[ScriptMethod(ResponseFormat.Json)]明確指定回傳 JSON 格式 (預設即為 JSON)。
List<T> 參數自動反序列化前端傳送 JSON 陣列,ASP.NET AJAX 自動反序列化為 `List`,屬性名稱需對應 (大小寫不敏感)。
動態列 ID 不衝突使用 `class` 選取器 (`.dept-name`) 而非 `id`,避免動態新增時 ID 重複問題。
資料收集邏輯`querySelectorAll` 遍歷 `tbody tr`,提取各 `input.value` 組成 DTO 陣列。
錯誤處理`onFailure` 捕捉網路/伺服器例外,`onSuccess` 處理業務邏輯失敗 (Success=false)。

進階擴充建議

  • 驗證框架整合:後端使用 `DataAnnotations` + 自訂驗證屬性,前端配合 jQuery Validation / ASP.NET AJAX Validation。
  • 樂觀鎖定:主表加 `RowVersion` (Timestamp),提交時帶回伺服器檢查並發衝突。
  • 檔案上傳支援:動態列含檔案上傳需改用 `FormData` + `$.ajax` / `fetch`,PageMethods 不支援 multipart/form-data。
  • 批次編輯/刪除:前端維护 `deletedIds` 陣列,提交時一併送至後端執行軟刪除。
  • 現代化替代:新專案建議使用 **ASP.NET Core Web API** + **Fetch API** / **Axios** + **Vue/React/Blazor**,架構更清晰。

常見問題排查

現象原因解決
PageMethods 未定義ScriptManager 缺少 `EnablePageMethods="true"` 或未引用 `MicrosoftAjax.js`檢查 ScriptManager 設定,確保頁面有 ``
WebMethod 404 / 500方法非 static、缺少 [WebMethod]、路徑錯誤 (需在同一 .aspx)確認方法簽章、類別名稱、命名空間
List 參數為 null / Count=0前端 JSON 屬性名稱與 DTO 不符 (如 `name` vs `Name`)確保前端物件屬性名稱與 DTO 完全一致 (建議 PascalCase)
動態列事件失效UpdatePanel 部分更新後事件未重新綁定本範例未用 UpdatePanel;若用需用 `PageRequestManager` 重新綁定
中文亂碼頁面編碼非 UTF-8 或 Web.config globalization 設定衝突頁面儲存為 UTF-8,web.config ``

總結

結合 **原生 JS 動態 DOM 操作** + **ASP.NET AJAX PageMethods**,即可輕鬆實現「主從表單、動態列、無刷新提交」的經典需求。核心在於:前端負責 UI 互動與資料收集 (DTO 序列化),後端 `static WebMethod` 負責業務邏輯與持久化。此模式在 Web Forms 專案中極具實用價值,也是邁向前後端分離架構的良好過渡練習。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved