
问题根源在于 disablepopup(true) 禁用了表单内所有输入控件,而 .always() 回调执行晚于 .done(),导致 trigger('submit') 时表单实际提交的数据为空(禁用字段被浏览器忽略),asp.net core 验证失败返回 400。
问题根源在于 disablepopup(true) 禁用了表单内所有输入控件,而 .always() 回调执行晚于 .done(),导致 trigger('submit') 时表单实际提交的数据为空(禁用字段被浏览器忽略),asp.net core 验证失败返回 400。
在 ASP.NET Core Razor Pages 应用中,表单提交依赖完整的、启用的输入字段(尤其是隐藏的防伪令牌 __RequestVerificationToken 和必填业务字段)。当您调用 disablePopup(true) 后立即发起 AJAX 请求,并在 .done() 中直接触发 $form.trigger('submit'),此时表单控件仍处于 disabled 状态——浏览器标准行为明确规定:禁用的 、。因此,最终提交的请求体中缺失关键字段(如 __RequestVerificationToken、SourceId、TargetType 等),触发服务器端模型绑定失败或防伪令牌验证失败,返回 HTTP 400 Bad Request。
您观察到 setTimeout 能“修复”问题,本质是利用了 JavaScript 事件循环的异步特性:.always() 回调(内部调用 disablePopup(false))虽注册在 .fail() 之后,但实际执行时机取决于 Promise 状态解析顺序;而 setTimeout(..., 0) 将表单提交推入下一个宏任务队列,确保 .always() 先执行、输入框被重新启用,再提交表单——但这是一种不可靠的“竞态修复”,延迟值(如 300ms)无理论依据,且易受环境影响。
✅ 正确解法:调整 jQuery Deferred 回调注册顺序,确保 disablePopup(false) 在 .done() 或 .fail() 前执行
$('#product-transfer-submit').on('click', function () {
disablePopup(true);
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();
$modal.find('#TargetType').val(targetType);
$.ajax({
type: 'GET',
url: '?handler=ValidateProductTransfer',
// ✅ 移除 contentType: 'application/json' —— GET 请求无请求体,设置此头无效且可能干扰服务器
dataType: 'json',
data: {
'sourceId': $modal.find('#SourceId').val(),
'targetType': targetType,
'targetId': targetId,
'productId': $modal.find('#ProductId').val()
}
})
.always(function () { // ? 关键:always 必须最先注册!
disablePopup(false);
})
.done(function (response) {
if (response.isSuccess) {
$form.trigger('submit'); // 此时所有字段已启用,提交完整数据
} else {
alert(response.error);
}
})
.fail(function (xhr) {
alert(xhr.responseText || 'AJAX request failed.');
});
});
? 关键要点总结:
- 禁用控件 ≠ 视觉灰化,而是彻底排除在表单数据之外:disabled 属性使元素不响应交互,且不参与 form.submit() 的默认序列化过程。
- jQuery 的 .always() 不保证“绝对最后执行”:其执行时机取决于 Promise 状态解析完成时间,但若 .done() 内同步触发提交,.always() 可能尚未运行——必须显式前置注册以确保优先级。
- GET 请求无需 contentType:contentType 仅用于设置请求体(request body)的 MIME 类型,而 GET 请求将参数编码在 URL 查询字符串中,设置 contentType 不但无效,还可能引发服务器端解析异常。
- 防伪令牌(Antiforgery Token)极易因此失效:Razor Pages 的 __RequestVerificationToken 隐藏字段若被禁用,将不会提交,导致 [ValidateAntiForgeryToken] 特性验证失败,直接返回 400。
? 进阶建议:
- 若需更精细控制加载状态,可改用 Promise.finally()(现代写法)或明确分离 UI 状态管理与业务逻辑;
- 考虑使用 event.preventDefault() + 手动构造 FormData 提交,避免依赖原生表单触发机制;
- 在服务端日志中检查 400 错误的具体原因(如 Invalid model state 或 Antiforgery token validation failed),可快速定位缺失字段。
遵循上述修正后,表单将在 AJAX 验证通过后、控件恢复启用状态下干净提交,彻底规避 400 错误,且无需任何脆弱的定时器 hack。











