
本文讲解如何解决 razor 页面中 foreach 循环生成多个删除按钮时,因复用同一 `
在 ASP.NET Core Razor Pages 中,使用 @foreach 渲染数据列表时,若为每行添加“删除”操作并配合 <dialog> 元素实现模态确认,一个常见陷阱是:所有对话框共用相同 id="deleteDialog",导致 showModal() 总是激活第一个(或 DOM 中首个匹配)的 <dialog>,进而使 <button asp-route-experimentId="@experiment.Id"> 实际提交的始终是循环中第一个 @experiment.Id —— 即便用户点击的是第 5 行的删除按钮。
根本原因在于:HTML id 属性必须全局唯一;而当前代码中,document.getElementById("deleteDialog") 每次都返回第一个匹配元素,与用户实际触发的行完全脱节。
✅ 正确做法是:为每个实验项生成唯一对话框 ID,并将 ID 作为参数传递给 JavaScript 函数。以下是完整、可直接运行的修复方案:
<form method="post">
<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach (var experiment in Model.MainExperimentsTests)
{
var dialogId = $"deleteDialog_{experiment.Id}";
<tr>
<td>@experiment.Id</td>
<td>@experiment.ExperimentTestName</td>
<td>
<a class="btn btn-sm btn-outline-danger permissionRead permissionWrite"
onclick="showDeleteDialog('@dialogId')">
<i class="bi-bucket"></i> Delete
</a>
<dialog id="@dialogId" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Confirm Deletion</h3>
<p>Are you sure you want to delete "<strong>@experiment.ExperimentTestName</strong>"?</p>
<div class="modal-action">
<button asp-page-handler="Delete"
asp-route-experimentId="@experiment.Id"
class="btn btn-error">Yes, Delete</button>
<button type="button" class="btn" onclick="closeDeleteDialog('@dialogId')">Cancel</button>
</div>
</div>
</dialog>
</td>
</tr>
}
</tbody>
</table>
</form>配套 JavaScript(需置于页面底部或 @section Scripts 中):
<script>
function showDeleteDialog(dialogId) {
const dialog = document.getElementById(dialogId);
if (dialog) {
dialog.showModal();
} else {
console.warn(`Dialog with id "${dialogId}" not found.`);
}
}
function closeDeleteDialog(dialogId) {
const dialog = document.getElementById(dialogId);
if (dialog) {
dialog.close();
}
}
// 可选:支持 Esc 键关闭当前打开的 dialog(增强体验)
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const openDialogs = document.querySelectorAll('dialog[open]');
if (openDialogs.length > 0) {
openDialogs[openDialogs.length - 1].close(); // 关闭最上层
}
}
});
</script>⚠️ 注意事项:
- ID 命名安全:确保 @experiment.Id 是 URL 安全字符串(如整型或 GUID)。若可能含特殊字符(空格、/、. 等),建议用 @experiment.Id.ToString().Replace(".", "_").Replace("/", "_") 预处理;
- 避免重复渲染:不要在循环内重复定义 <script> 块,所有 JS 函数应在页面级统一声明一次;
- 表单提交行为:<button asp-page-handler="Delete"> 会提交整个 <form>,因此需确保该 form 不包含其他干扰字段;如需更精细控制(如 AJAX 删除),可移除 asp-* 属性,改用 fetch() + AntiForgeryToken 手动提交;
- 样式兼容性:原生 <dialog> 在 Safari 中需启用实验性功能(iOS 16.4+/macOS 13.3+ 默认支持),生产环境建议搭配 dialog-polyfill 或使用 Bootstrap Modal / Blazored.Modal 等成熟组件。
总结:Razor 循环中动态交互的关键在于「将服务端上下文(如 @experiment.Id)可靠地桥接到客户端事件流」。通过唯一 ID 绑定 + 参数化 JS 调用,既保留了 asp-route-* 的简洁路由能力,又彻底规避了 DOM 查找歧义问题——这是服务端渲染与前端交互协同设计的经典实践。










