You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不提交完整ViewModel实现POST请求且错误时保留视图现有ViewModel数据

最优方案:同域AJAX调用MVC本地Action(无需修改Identity Server配置)

你之前对身份校验的顾虑是多余的:

  • 同域下AJAX请求会自动携带MVC的认证Cookie,服务端可以正常识别用户身份
  • 服务端调用后端API的逻辑和普通页面请求完全一致,DelegatingHandler获取AccessToken的流程不受影响,不会出现未授权问题
  • 全程不需要JS接触AccessToken,也不需要开启Implicit流,完全符合Code流的安全设计

实现代码

  1. 详情页添加全局防伪标记和错误展示区域
<!-- 页面任意位置加一次即可 -->
@Html.AntiForgeryToken()
<!-- 错误提示区域 -->
<div class="alert alert-danger d-none" id="errorBox"></div>
  1. 晋升按钮绑定数据属性
<button class="btn btn-promote" 
        data-organization-id="@Model.Id" 
        data-member-id="@member.Id">
    晋升
</button>
  1. JS请求逻辑
$('.btn-promote').click(async function() {
    const orgId = $(this).data('organization-id');
    const memberId = $(this).data('member-id');
    const token = $('input[name="__RequestVerificationToken"]').val();

    try {
        const res = await fetch('/Member/Promote', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
                'RequestVerificationToken': token
            },
            body: `organizationId=${orgId}&memberId=${memberId}`
        });
        const result = await res.json();
        if (result.success) {
            // 成功直接跳转到详情页,自动重置筛选状态
            window.location.href = `/Organization/Details?organizationId=${orgId}`;
        } else {
            // 失败直接展示错误,页面分页、筛选状态完全保留
            $('#errorBox').text(result.errors.join(';')).removeClass('d-none');
        }
    } catch (e) {
        $('#errorBox').text('操作失败,请重试').removeClass('d-none');
    }
});
  1. 后端Promote方法调整
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Promote(Guid organizationId, Guid memberId)
{
    var result = await _memberService.Promote(organizationId, memberId);
    if (result.IsFailure)
    {
        ModelState.AddModelError(string.Empty, result.Error);
        return Json(new 
        {
            success = false,
            errors = ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage)).ToList()
        });
    }
    return Json(new { success = true });
}

备选方案:纯服务端PRG模式(无需写JS)

你之前对TempData的顾虑是误解:ASP.NET Core默认的TempData使用CookieTempDataProvider,不需要开启会话,数据加密后存在客户端Cookie中,仅存储搜索参数和错误信息的数据量极小,完全符合规范。

实现代码

  1. 晋升按钮表单仅提交必要参数
<form method="post" asp-action="Promote" asp-controller="Member">
    <input type="hidden" name="organizationId" value="@Model.Id" />
    <input type="hidden" name="memberId" value="@member.Id" />
    <!-- 仅序列化SearchMetadata字段为隐藏域 -->
    @Html.HiddenFor(m => m.SearchMetadata.PageIndex)
    @Html.HiddenFor(m => m.SearchMetadata.PageSize)
    @Html.HiddenFor(m => m.SearchMetadata.SearchKeyword)
    <!-- 其他筛选字段同理 -->
    <button type="submit" class="btn btn-promote">晋升</button>
</form>
  1. 后端Promote方法逻辑
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Promote(Guid organizationId, Guid memberId, MembersResourceParameters searchMetadata)
{
    var result = await _memberService.Promote(organizationId, memberId);
    if (result.IsFailure)
    {
        // 存错误和搜索参数到TempData
        TempData["OperationError"] = result.Error;
        TempData["LastSearchMetadata"] = JsonSerializer.Serialize(searchMetadata);
        return RedirectToAction("Details", "Organization", new { organizationId });
    }
    // 成功直接跳转,不传递搜索参数,自动重置状态
    return RedirectToAction("Details", "Organization", new { organizationId });
}
  1. 调整OrganizationController的Details方法
public async Task<IActionResult> Details(Guid organizationId)
{
    MembersResourceParameters searchParam = new();
    // 读取TempData中的历史搜索参数
    if (TempData["LastSearchMetadata"] is string json)
    {
        searchParam = JsonSerializer.Deserialize<MembersResourceParameters>(json);
        // 注入错误信息到模型状态
        if (TempData["OperationError"] is string error)
        {
            ModelState.AddModelError(string.Empty, error);
        }
        // 可选:保留TempData防止刷新丢失错误
        TempData.Keep();
    }
    // 用搜索参数请求API获取视图模型
    var orgData = await _organizationService.GetDetails(organizationId, searchParam);
    var model = _mapper.Map<OrganizationDetailsViewModel>(orgData);
    return View(model);
}

该方案URL永远为详情页地址,完全符合你的需求。


内容的提问来源于stack exchange,提问作者RocH

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 17:48:04