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

C#中表单异步提交后发起PUT请求及响应回传的实现疑问

解决方案

一、页面加载时立即发起PUT请求的正确方式

你当前的实现存在几个明显问题:

  • 在Razor视图中直接调用异步方法ServiceActivation()会导致同步阻塞,且private static方法无法被视图直接访问
  • 每次新建HttpClient会耗尽连接池资源,应当复用实例

正确的做法是将PUT请求逻辑放在控制器的Action中,表单提交的目标直接指向该Action,而非跳转视图页面:

1. 重构后台控制器逻辑

public class ActivationController : Controller
{
    private readonly HttpClient _httpClient;

    // 通过依赖注入复用HttpClient(官方推荐做法)
    public ActivationController(HttpClient httpClient)
    {
        _httpClient = httpClient;
        // 初始化默认请求头
        _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    // 接收表单提交的Action
    [HttpPost]
    public async Task<IActionResult> ProcessActivation(FormValues model)
    {
        if (!ModelState.IsValid)
        {
            TempData["ActivationError"] = "表单数据无效";
            return RedirectToAction("Index", "Home"); // 跳转回原激活页面
        }

        var user = User.GetUser();
        _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token());
        
        var requestUri = $"https://somedomain/api/{model.Id}/activate-service/{model.SubId}";
        var json = JsonConvert.SerializeObject(model);
        var httpContent = new StringContent(json, Encoding.UTF8, "application/json");

        try
        {
            var response = await _httpClient.PutAsync(requestUri, httpContent);
            response.EnsureSuccessStatusCode();
            var content = await response.Content.ReadAsStringAsync();
            
            // 保存成功结果
            TempData["ActivationSuccess"] = $"服务激活成功:{content}";
        }
        catch (HttpRequestException ex)
        {
            // 捕获请求异常并保存错误信息
            TempData["ActivationError"] = $"激活失败:{ex.Message}";
        }

        return RedirectToAction("Index", "Home");
    }

    // 表单数据模型(建议放到单独的Models文件夹下)
    public class FormValues
    {
        public string Id { get; set; }
        public string SubId { get; set; }
    }
}

核心说明

  • 表单提交直接触发ProcessActivation Action,Action加载时自动执行PUT请求逻辑
  • 依赖注入HttpClient避免重复创建实例,提升性能与稳定性
  • 用await替代.Result,彻底避免异步阻塞问题

二、将响应结果传回原激活页面

方式1:使用TempData传递(适合页面跳转场景)

在原激活页面的Razor视图中,读取TempData中的信息并展示:

<!-- 原激活页面视图 -->
@if (TempData["ActivationSuccess"] != null)
{
    <div class="alert alert-success">@TempData["ActivationSuccess"]</div>
}

@if (TempData["ActivationError"] != null)
{
    <div class="alert alert-danger">@TempData["ActivationError"]</div>
}

<!-- 激活按钮表单 -->
<form asp-action="ProcessActivation" asp-controller="Activation" method="post">
    <input type="hidden" name="Id" value="xxx" />
    <input type="hidden" name="SubId" value="yyy" />
    <button type="submit" class="btn btn-primary">激活服务</button>
</form>

方式2:AJAX异步提交(推荐,无页面刷新)

如果要实现真正的异步无刷新提交,可以直接在原页面用AJAX发起请求,无需跳转至目标页面:

原激活页面视图代码

<!-- 提示信息容器 -->
<div id="activationMsg" class="alert d-none"></div>

<!-- 激活按钮 -->
<button id="activateBtn" class="btn btn-primary" data-id="xxx" data-subid="yyy">激活服务</button>

<script>
document.getElementById('activateBtn').addEventListener('click', async function() {
    const btn = this;
    btn.disabled = true;
    const msgDiv = document.getElementById('activationMsg');
    msgDiv.className = 'alert d-none';

    const id = btn.dataset.id;
    const subId = btn.dataset.subid;

    try {
        const response = await fetch('/Activation/ProcessActivationApi', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'RequestVerificationToken': document.querySelector('input[name="__RequestVerificationToken"]').value
            },
            body: JSON.stringify({ Id: id, SubId: subId })
        });

        const result = await response.json();
        if (result.success) {
            msgDiv.textContent = `服务激活成功:${result.data}`;
            msgDiv.className = 'alert alert-success';
        } else {
            msgDiv.textContent = `激活失败:${result.error}`;
            msgDiv.className = 'alert alert-danger';
        }
    } catch (ex) {
        msgDiv.textContent = `请求出错:${ex.message}`;
        msgDiv.className = 'alert alert-danger';
    } finally {
        btn.disabled = false;
    }
});
</script>

新增API Action用于AJAX请求

[HttpPost]
[Route("Activation/ProcessActivationApi")]
public async Task<IActionResult> ProcessActivationApi([FromBody] FormValues model)
{
    if (!ModelState.IsValid)
    {
        return Json(new { success = false, error = "表单数据无效" });
    }

    var user = User.GetUser();
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", user.Token());
    
    var requestUri = $"https://somedomain/api/{model.Id}/activate-service/{model.SubId}";
    var json = JsonConvert.SerializeObject(model);
    var httpContent = new StringContent(json, Encoding.UTF8, "application/json");

    try
    {
        var response = await _httpClient.PutAsync(requestUri, httpContent);
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        
        return Json(new { success = true, data = content });
    }
    catch (HttpRequestException ex)
    {
        return Json(new { success = false, error = ex.Message });
    }
}

额外优化建议

  • 将FormValues模型类移至单独的Models文件夹,遵循MVC分层规范
  • 对用户Token做有效性校验,避免空Token导致请求失败
  • 把远程API地址配置到appsettings.json中,避免硬编码不利于维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:10:23