頁面初次載入需呈現大量資料(如 6000 筆),且支援前端即時增刪改、最後一次性儲存。傳統 PostBack 會導致大量 ViewState、回應緩慢。本文展示 **ASP.NET AJAX (ScriptManager/PageMethods) + JSON 序列化 + jQuery 前端渲染** 的完整架構。
<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
<Scripts>
<asp:ScriptReference Path="~/Scripts/jquery.js" />
</Scripts>
</asp:ScriptManager>
<asp:Repeater ID="rptData" runat="server">
<HeaderTemplate><table class="grid"><thead><tr><th>機構</th><th>群組</th><th>操作</th></tr></thead><tbody></HeaderTemplate>
<ItemTemplate>
<tr id='row-<%# Eval("Id") %>'>
<td><%# Eval("OrgName") %></td>
<td><%# Eval("GroupName") %></td>
<td><a href="javascript:deleteRow('<%# Eval("Id") %>')">刪除</a></td>
</tr>
</ItemTemplate>
<FooterTemplate></tbody></table></FooterTemplate>
</asp:Repeater>
<asp:UpdatePanel ID="upPanel" runat="server">
<ContentTemplate>
序列化 JSON 快取:<br/>
<asp:TextBox ID="txtJsonCache" runat="server" TextMode="MultiLine" Rows="10" Width="800px" /><br/>
新增 ID:<asp:TextBox ID="txtNewId" runat="server" Width="200px" />
<input type="button" value="新增" onclick="addRow()" />
</ContentTemplate>
</asp:UpdatePanel>
private List _dataSource = new List();
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
InitDataSource();
BindRepeater();
SerializeToJsonCache();
}
}
private void InitDataSource()
{
for (int i = 0; i < 6000; i++)
{
_dataSource.Add(new DataEntity
{
Id = i.ToString(),
OrgName = $"{i}-1",
GroupName = $"{i}-2"
});
}
}
private void BindRepeater()
{
rptData.DataSource = _dataSource;
rptData.DataBind();
}
private void SerializeToJsonCache()
{
var jsonObj = new JSONObject();
for (int i = 0; i < _dataSource.Count; i++)
{
var item = _dataSource[i];
var arr = new JSONArray { item.Id, item.OrgName, item.GroupName };
jsonObj.Add(i.ToString(), arr);
}
txtJsonCache.Text = JSONConvert.SerializeObject(jsonObj); // 自訂 JSON 序列化
}
<script type="text/javascript">
var jsonCacheId = '<%= txtJsonCache.ClientID %>';
var newIdInputId = '<%= txtNewId.ClientID %>';
var tableBodyId = 'rptData_tbody'; // 需在繼承模板中加入 ID
// 刪除列
function deleteRow(id) {
// 1. 呼叫 PageMethod 刪除快取項
$('#' + jsonCacheId).val(id); // 暫存要刪除的 ID
$('#<%= btnDeleteCache.ClientID %>').click(); // 觸發隱藏按鈕 PageMethod
// 2. 立即移除 DOM
$('#row-' + id).hide();
}
// 新增列
function addRow() {
var id = $('#' + newIdInputId).val();
if (!id) return alert('請輸入 ID');
var json = $('#' + jsonCacheId).val();
if (json.indexOf(id) > -1) return alert('ID 已存在');
// 呼叫 PageMethod 查詢完整資料
PageMethods.GetItemById(id, onAddItemSuccess);
}
function onAddItemSuccess(result) {
if (result == "error") return alert('查無資料');
// 1. 渲染新列
var html = buildRowHtml(result);
$('#' + tableBodyId).append(html);
// 2. 更新 JSON 快取
updateJsonCache(result);
}
function buildRowHtml(jsonStr) {
var data = eval('(' + jsonStr + ')'); // 解析 JSON 字串
var tpl = '<tr id="row-{id}"><td>{org}</td><td>{group}</td><td><a href="javascript:deleteRow('{id}')">刪除</a></td></tr>';
$.each(data, function(i, item) {
tpl = tpl.replace('{id}', item[0])
.replace('{org}', item[1])
.replace('{group}', item[2]);
});
return tpl;
}
function updateJsonCache(itemJson) {
var $cache = $('#' + jsonCacheId);
var old = $cache.val();
itemJson = itemJson.replace('{', '');
var newJson = old == '{}' ? old.replace('}', itemJson) : old.replace('}', ',' + itemJson);
$cache.val(newJson);
}
// 儲存全部變更
function saveAll() {
var json = $('#' + jsonCacheId).val();
PageMethods.SaveAllChanges(json, onSaveComplete);
}
function onSaveComplete(result) {
alert(result ? '儲存成功' : '儲存失敗');
}
</script>
[WebMethod]
public static string GetItemById(string id)
{
// 模擬查詢 DB
var item = DataRepository.GetById(id);
if (item == null) return "error";
return $"{{\"Id\":\"{item.Id}\",\"OrgName\":\"{item.OrgName}\",\"GroupName\":\"{item.GroupName}\"}}";
}
[WebMethod]
public static bool SaveAllChanges(string json)
{
try
{
var data = JSONConvert.DeserializeObject(json); // 自訂反序列化
foreach (var kvp in data)
{
var arr = (JSONArray)kvp.Value;
var entity = new DataEntity
{
Id = arr[0].ToString(),
OrgName = arr[1].ToString(),
GroupName = arr[2].ToString()
};
DataRepository.Upsert(entity);
}
return true;
}
catch { return false; }
}
public static class JSONConvert
{
public static string SerializeObject(JSONObject obj) { /* ... */ }
public static JSONObject DeserializeObject(string json) { /* ... */ }
// 內部用正則解析、轉義 ; , { } [ ] " 等特殊字元
}
public class JSONObject : Dictionary { /* 字串值自動轉義 */ }
public class JSONArray : List
| 決策點 | 方案 | 理由 |
|---|---|---|
| 首次渲染 | Server 端 Repeater | SEO 友善、首屏速度快、無閃爍 |
| 客戶端快取 | 隱藏 TextBox 存 JSON | UpdatePanel 部分更新自動同步、無需 Session |
| 前端增刪改 | jQuery DOM 操作 + JSON 同步 | 零 PostBack、體驗流暢 |
| 序列化 | 自訂 JSONObject/JSONArray | .NET 2.0 無內建 JSON、避免引入第三方 DLL |
| 批次儲存 | PageMethod 接收完整 JSON | 單次請求、交易性處理、減少網路往返 |