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

如何在调用ASP.NET Core 3.1 Web API的ASP.NET MVC应用中结合HttpContent处理并发冲突(409状态码)错误?

处理ASP.NET MVC中API返回的409冲突错误

你遇到的问题很典型——当错误处理逻辑分散在Repo、服务和控制器层时,如何让上层代码能清晰识别特定的HTTP错误状态,而不是靠解析异常消息这种不够优雅的方式。下面给你几个可行的方案,按推荐程度排序:

1. 创建自定义异常(最推荐)

这是类型安全、可读性最高的方式。你可以定义一个专门的异常类来标识409冲突场景,这样控制器里能直接捕获这个特定异常,逻辑非常清晰。

首先,创建自定义异常:

public class ConflictException : Exception
{
    // 基本构造函数,带错误消息
    public ConflictException(string message) : base(message) { }

    // 带内部异常的构造函数,保留原始错误信息用于调试
    public ConflictException(string message, Exception innerException) 
        : base(message, innerException) { }
}

然后修改你的UserRepo.Update方法,在捕获到409时抛出这个自定义异常,而不是原始的HttpRequestException:

public async Task<User> Update(User user) {
    HttpClient client = _clientFactory.CreateClient("namedClient");
    HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
    
    try {
        response.EnsureSuccessStatusCode();
    } catch (Exception ex) {
        if (response.StatusCode == StatusCodes.Status409Conflict) {
            // 抛出自定义异常,带上友好提示和原始异常
            throw new ConflictException("用户数据已过时,请刷新页面后重试", ex);
        }
        // 其他错误继续抛出,让上层处理或者统一捕获
        throw;
    }
    
    return await response.Content.ReadFromJsonAsync<User>();
}

最后在控制器里,直接捕获ConflictException并处理:

public async Task<IActionResult> Update(UserViewModel model)
{
    try
    {
        var user = MapViewModelToUser(model);
        await _userRepo.Update(user);
        return RedirectToAction("Index");
    }
    catch (ConflictException ex)
    {
        // 给用户显示友好提示
        ModelState.AddModelError(string.Empty, ex.Message);
        // 回显用户输入的数据,或者刷新最新数据后返回视图
        return View(model);
    }
    catch (Exception ex)
    {
        // 处理其他未知错误
        ModelState.AddModelError(string.Empty, "更新失败,请稍后重试");
        return View(model);
    }
}

这种方式的优势在于:上层代码不需要关心底层HTTP状态码的细节,只需要处理业务层面的异常类型,代码更易维护,也符合异常处理的最佳实践(用异常表示异常情况,而非正常流程)。

2. 给现有异常添加额外数据(备选)

如果不想创建新的异常类,你可以利用Exception.Data字典来附加状态码信息,这样控制器里可以通过这个字典判断错误类型。

修改UserRepo的catch块:

catch (Exception ex) {
    if (response.StatusCode == StatusCodes.Status409Conflict) {
        ex.Data["StatusCode"] = (int)StatusCodes.Status409Conflict;
        throw;
    }
    throw;
}

然后在控制器里捕获并检查:

catch (HttpRequestException ex)
{
    if (ex.Data.Contains("StatusCode") && (int)ex.Data["StatusCode"] == StatusCodes.Status409Conflict)
    {
        ModelState.AddModelError(string.Empty, "用户数据已过时,请刷新后重试");
        return View(model);
    }
    // 其他HttpRequestException处理
}

这种方式的缺点是不够类型安全——你需要确保键名拼写正确,而且如果底层抛出的不是HttpRequestException,还需要额外判断,可读性和维护性不如自定义异常。

3. 返回结果对象(无异常方案)

如果你的团队偏好不用异常来控制业务流程,可以定义一个通用的结果类,让Repo方法返回操作状态、数据和错误信息,而不是抛出异常。

首先定义结果类:

public class RepositoryResult<T>
{
    public bool IsSuccess { get; set; }
    public T Data { get; set; }
    public string ErrorMessage { get; set; }
    public int? StatusCode { get; set; }
}

修改UserRepo.Update方法返回这个结果:

public async Task<RepositoryResult<User>> Update(User user) {
    HttpClient client = _clientFactory.CreateClient("namedClient");
    HttpResponseMessage response = await client.PutAsync($"api/Users/{user.Id}", ContentEncoder.Encode(user));
    
    if (response.IsSuccessStatusCode)
    {
        var updatedUser = await response.Content.ReadFromJsonAsync<User>();
        return new RepositoryResult<User> 
        { 
            IsSuccess = true, 
            Data = updatedUser 
        };
    }
    else if (response.StatusCode == StatusCodes.Status409Conflict)
    {
        return new RepositoryResult<User> 
        { 
            IsSuccess = false, 
            ErrorMessage = "用户数据已过时,请刷新后重试",
            StatusCode = (int)StatusCodes.Status409Conflict
        };
    }
    else
    {
        // 其他错误情况,返回通用错误信息
        return new RepositoryResult<User> 
        { 
            IsSuccess = false, 
            ErrorMessage = "更新失败,请稍后重试",
            StatusCode = (int)response.StatusCode
        };
    }
}

然后控制器里直接判断结果:

public async Task<IActionResult> Update(UserViewModel model)
{
    var user = MapViewModelToUser(model);
    var result = await _userRepo.Update(user);
    
    if (result.IsSuccess)
    {
        return RedirectToAction("Index");
    }
    else if (result.StatusCode == StatusCodes.Status409Conflict)
    {
        ModelState.AddModelError(string.Empty, result.ErrorMessage);
        return View(model);
    }
    else
    {
        ModelState.AddModelError(string.Empty, result.ErrorMessage);
        return View(model);
    }
}

这种方案适合那些认为“异常应该只用于真正的意外错误”的场景,但会让Repo方法的返回值更复杂,需要上层代码手动处理各种状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 04:47:48