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

.NET多应用跨API控制器的自定义异常处理方案咨询

跨应用API调用的异常处理与标准化响应方案

现有问题分析

当前App1的Controller A调用App2的Controller B时,App2抛出的CustomException自定义消息无法直接传递到App1——默认HTTP客户端会将非成功响应包装为APIException,导致App1只能返回自身预设的通用错误信息。临时通过捕获APIException读取响应内容的方式,缺乏标准化,也无法优雅区分不同异常场景。

长期可行解决方案

1. 标准化错误响应格式(核心推荐)

为所有服务定义统一的结构化响应类,无论成功/失败都返回该格式,确保错误信息、状态码等元数据能完整传递:

通用响应类定义

public class ApiResponse<T>
{
    public bool Success { get; set; }
    public T Data { get; set; }
    public string ErrorMessage { get; set; }
    public int StatusCode { get; set; }
    // 可选扩展:添加RequestId、Timestamp、ErrorCode等排查字段
}

App2的Controller B改造

public ActionResult<ApiResponse<ExpectedType>> GetThatData(QueryModelType model)
{
    try
    {
        var data = _dataFetchingDAO.GetDataFromDB(model);
        return Ok(new ApiResponse<ExpectedType>
        {
            Success = true,
            Data = data,
            StatusCode = 200
        });
    }
    catch (CustomException customEx)
    {
        _logger.Log(customEx);
        return StatusCode(500, new ApiResponse<ExpectedType>
        {
            Success = false,
            ErrorMessage = customEx.Message,
            StatusCode = 500
        });
    }
    catch (Exception ex)
    {
        _logger.Log(ex);
        return StatusCode(500, new ApiResponse<ExpectedType>
        {
            Success = false,
            ErrorMessage = "App2服务内部错误",
            StatusCode = 500
        });
    }
}

App1调用层(_secondaryAppWrapper)改造

public async Task<ApiResponse<ExpectedType>> GetThatData(QueryModelType model)
{
    var httpResponse = await _httpClient.PostAsJsonAsync("/api/controllerB/getthatdata", model);
    var response = await httpResponse.Content.ReadFromJsonAsync<ApiResponse<ExpectedType>>();
    
    if (!httpResponse.IsSuccessStatusCode)
    {
        response.StatusCode = (int)httpResponse.StatusCode;
    }
    return response;
}

App1的Controller A处理逻辑

public async Task<ActionResult<ApiResponse<ExpectedType>>> GetData(ModelType model)
{
    try
    {
        var apiResponse = await _dataFetchingBizLayer.GetThatDataFromApp2(model);
        return StatusCode(apiResponse.StatusCode, apiResponse);
    }
    catch (Exception ex)
    {
        _logger.Log(ex);
        return StatusCode(500, new ApiResponse<ExpectedType>
        {
            Success = false,
            ErrorMessage = "App1服务内部错误",
            StatusCode = 500
        });
    }
}

2. 全局异常处理中间件(简化App2代码)

在App2中添加全局异常中间件,自动捕获所有异常并转换为标准化响应,无需每个Controller编写重复的try-catch:

中间件实现

public class ExceptionHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ExceptionHandlingMiddleware> _logger;

    public ExceptionHandlingMiddleware(RequestDelegate next, ILogger<ExceptionHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (CustomException ex)
        {
            _logger.LogError(ex, "App2自定义异常");
            await WriteErrorResponse(context, ex.Message, StatusCodes.Status500InternalServerError);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "App2未处理异常");
            await WriteErrorResponse(context, "服务内部错误", StatusCodes.Status500InternalServerError);
        }
    }

    private static async Task WriteErrorResponse(HttpContext context, string errorMsg, int statusCode)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = statusCode;
        
        var errorResponse = new ApiResponse<object>
        {
            Success = false,
            ErrorMessage = errorMsg,
            StatusCode = statusCode
        };
        
        await context.Response.WriteAsJsonAsync(errorResponse);
    }
}

注册中间件(App2的Program.cs)

app.UseMiddleware<ExceptionHandlingMiddleware>();

改造后App2的Controller B可简化为:

public ActionResult<ExpectedType> GetThatData(QueryModelType model)
{
    // 直接抛出异常,由中间件统一处理
    return _dataFetchingDAO.GetDataFromDB(model);
}

3. 调用层异常映射(优化App1业务逻辑)

在App1的调用层将标准化响应映射为自定义异常,让上层业务逻辑能针对性处理不同错误场景:

public async Task<ExpectedType> GetThatData(QueryModelType model)
{
    var httpResponse = await _httpClient.PostAsJsonAsync("/api/controllerB/getthatdata", model);
    if (httpResponse.IsSuccessStatusCode)
    {
        var apiResponse = await httpResponse.Content.ReadFromJsonAsync<ApiResponse<ExpectedType>>();
        return apiResponse.Data;
    }
    else
    {
        var errorResponse = await httpResponse.Content.ReadFromJsonAsync<ApiResponse<object>>();
        var statusCode = (int)httpResponse.StatusCode;
        
        // 根据错误信息或状态码抛出对应异常
        if (errorResponse?.ErrorMessage.Contains("station B") == true)
        {
            throw new App2StationBException(errorResponse.ErrorMessage, statusCode);
        }
        else
        {
            throw new ApiCallException($"调用App2失败: {errorResponse?.ErrorMessage}", statusCode);
        }
    }
}

App1的Controller A可针对性捕获异常:

public async Task<ActionResult<ExpectedType>> GetData(ModelType model)
{
    try
    {
        return await _dataFetchingBizLayer.GetThatDataFromApp2(model);
    }
    catch (App2StationBException ex)
    {
        _logger.Log(ex);
        return StatusCode(ex.StatusCode, ex.Message);
    }
    catch (ApiCallException ex)
    {
        _logger.Log(ex);
        return StatusCode(ex.StatusCode, ex.Message);
    }
    catch (Exception ex)
    {
        _logger.Log(ex);
        return StatusCode(500, "App1服务内部错误");
    }
}

自定义响应类方案的核心优势

你考虑的自定义响应类是非常适合长期落地的方案,核心优势包括:

  • 标准化统一:所有服务响应格式一致,调用方无需适配多种结构
  • 扩展性强:可轻松添加请求ID、时间戳等元数据,便于问题排查
  • 兼容性好:成功/失败响应使用同一解析逻辑,降低出错概率
  • 语义清晰:明确区分成功/失败状态,避免错误数据被当作正常数据处理

总结

优先推荐标准化错误响应格式+全局异常中间件的组合方案,既能统一处理App2的异常输出,又能让App1完整获取自定义错误信息与状态码。结合调用层的异常映射,可进一步优化App1的业务逻辑处理效率,实现不同错误场景的差异化应对。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 18:26:00