
该问题本质是因表单控件被禁用后未及时恢复,导致提交时缺失必需字段(如防伪令牌、隐藏域等),ASP.NET Core 后端校验失败返回 400;正确做法是在 always 回调中优先恢复表单控件状态,再触发提交。
该问题本质是因表单控件被禁用后未及时恢复,导致提交时缺失必需字段(如防伪令牌、隐藏域等),asp.net core 后端校验失败返回 400;正确做法是在 `always` 回调中**优先恢复表单控件状态**,再触发提交。
在 ASP.NET Core Razor Pages 应用中,使用 jQuery 的 $ajax() 进行前置验证后自动提交表单是一种常见模式。但若在 .done() 回调中直接调用 $form.trigger('submit'),却频繁遭遇 HTTP 400 错误(Bad Request),其根本原因并非 AJAX 或表单逻辑本身有误,而是DOM 状态管理时序不当。
? 根本原因:禁用控件导致关键字段丢失
从你提供的代码可见,disablePopup(true) 会禁用模态框内所有 和 元素:
function disablePopup(disable) {
$("#transfer-product-modal :input").prop("disabled", disable);
$("#transfer-product-modal a").prop("disabled", disable);
}而 HTML 表单规范明确规定:disabled 的 元素不会参与表单序列化提交——这意味着以下关键字段将被完全忽略:
- 隐藏的防伪令牌(__RequestVerificationToken)
- 必填的隐藏字段(如 SourceId、TargetType、ProductId 等)
- 所有被禁用的
当 $form.trigger('submit') 在 .done() 中立即执行时,此时 disablePopup(false) 尚未运行(因 .always() 是异步队列中后注册、后执行的),表单实际以「部分字段缺失」状态提交,后端模型绑定失败或防伪令牌校验不通过,最终返回 400。
✅ setTimeout(..., 300) 能“凑巧”生效,是因为它把提交延迟到浏览器事件循环下一帧,恰好让 .always() 完成执行、控件恢复启用——但这属于不可靠的竞态修复,不应作为正式方案。
✅ 正确解决方案:调整回调注册顺序
核心原则是:确保表单控件在提交前已恢复启用状态。最佳实践是将 .always() 回调置于 .done() 和 .fail() 之前注册,利用 jQuery Deferred 的回调执行顺序保证其优先执行:
$('#product-transfer-submit').on('click', function () {
var $form = $(this).closest('form');
var $modal = $('#transfer-product-modal');
var $activeTab = $modal.find('a.nav-link.active');
var targetType = $activeTab.data('type');
var targetId = $modal.find($activeTab.attr('href') + ' :input').val();
// 禁用交互,防止重复点击
disablePopup(true);
$.ajax({
url: '?handler=ValidateProductTransfer',
method: 'GET',
// ⚠️ 注意:GET 请求无需设置 contentType,数据自动拼接为 query string
data: {
sourceId: $modal.find('#SourceId').val(),
targetType: targetType,
targetId: targetId,
productId: $modal.find('#ProductId').val()
},
dataType: 'json'
})
// ✅ 关键:always 必须最先注册,确保控件恢复优先于 submit 触发
.always(function () {
disablePopup(false); // 立即恢复所有输入框和链接
})
.done(function (response) {
if (response.isSuccess) {
$form.trigger('submit'); // 此时所有字段(含 __RequestVerificationToken)均有效
} else {
alert(response.error || '验证失败');
}
})
.fail(function (xhr) {
alert(xhr.responseText || '请求失败,请检查网络');
});
});? 关键注意事项与优化建议
- 移除冗余配置:contentType: 'application/json' 对 GET 请求无效且可能干扰默认行为,应删除;
-
防伪令牌安全:确保 __RequestVerificationToken 字段未被禁用(它属于 ,受 :input 选择器影响),否则即使恢复启用也无济于事——推荐在 disablePopup() 中显式排除它:
function disablePopup(disable) { // 排除防伪令牌和隐藏字段,确保它们始终参与提交 $("#transfer-product-modal :input:not([name='__RequestVerificationToken']):not([type='hidden'])") .prop("disabled", disable); $("#transfer-product-modal a").prop("disabled", disable); } -
增强健壮性:可添加提交前二次校验,例如检查 __RequestVerificationToken 是否存在且非空:
.done(function (response) { if (response.isSuccess) { const token = $form.find('input[name="__RequestVerificationToken"]').val(); if (!token) { console.error('防伪令牌缺失,无法提交'); return; } $form.trigger('submit'); } else { alert(response.error); } })
✅ 总结
| 问题现象 | 400 Bad Request 报错 |
|---|---|
| 根本原因 | disabled 控件不参与表单提交 → 防伪令牌/必填字段丢失 → 后端模型绑定或 Anti-Forgery 校验失败 |
| 错误修复方式 | setTimeout(掩盖时序问题,不可靠) |
| 正确解决方式 | 调整 jQuery Deferred 回调顺序:.always() 必须先注册,确保控件恢复后再触发提交 |
| 额外加固 | 排除关键隐藏字段于禁用逻辑之外,提交前做轻量校验 |
遵循此方案,即可彻底规避 400 错误,在保持用户体验(按钮禁用防重复)的同时,确保表单提交的完整性和安全性。


















