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

.NET Core 3.1 WebApi向Vue应用返回用户错误的优雅方案咨询

落地方案:基于.NET Core 3.1 WebApi的统一错误返回机制

核心思路调整

彻底放弃“所有业务返回类继承BaseDataObject”的设计——这套设计的本质问题是把错误元数据和业务模型强绑定,导致接口返回的JSON结构随业务类型(单对象/列表)变化,前端需要写额外分支判断。
替换为独立于业务模型的全局统一响应外层结构,保证不管接口返回的是单条对象、列表、基本类型,外层JSON格式完全固定,前端不需要做任何结构判断。
统一响应类定义:

/// <summary>
/// 全局固定API响应外层结构
/// </summary>
public class ApiResponse
{
    /// <summary>
    /// 错误严重级别/类型,无错误时为null
    /// </summary>
    public string ErrorType { get; set; }
    
    /// <summary>
    /// 面向用户展示的错误信息,无错误时为null
    /// </summary>
    public string ErrorMsg { get; set; }
    
    /// <summary>
    /// 实际业务数据,单对象/列表/空值都存在该字段下
    /// </summary>
    public object Data { get; set; }
}

这套结构直接解决第一个痛点:

  • 不管接口返回的是单条业务对象、还是业务对象列表,前端始终可以固定流程:先检查errorMsg字段是否存在,存在则弹窗展示错误;不存在则直接从data字段读取实际业务内容,不需要判断data是单对象还是数组
  • 不需要让任何业务类继承错误基类,没有冗余字段,序列化后的JSON体积更轻

全局统一异常处理:删除所有接口内的重复catch逻辑

基于.NET Core的中间件机制实现全局异常捕获,彻底替代每个Action里手写try-catch的重复代码,覆盖所有业务错误场景:

第一步:定义业务异常类

和原有体系保持一致:

public class ApiErrorException : Exception
{
    /// <summary>
    /// 自定义错误严重级别
    /// </summary>
    public string Type { get; }
    public ApiErrorException(string message, string type = "warning") : base(message)
    {
        Type = type;
    }
}

第二步:编写全局异常处理中间件

统一捕获所有抛出的异常,按照统一结构返回响应:

public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionMiddleware> _logger;
    public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (ApiErrorException ex)
        {
            // 预期内的业务校验错误,打警告日志,不需要记录全堆栈
            _logger.LogWarning(ex, "业务校验触发错误");
            await WriteResponse(context, ex.Message, ex.Type, null);
        }
        catch (Exception ex)
        {
            // 未捕获的系统异常,打错误日志带堆栈,返回通用友好提示
            _logger.LogError(ex, "系统未处理异常");
            await WriteResponse(context, "系统繁忙,请稍后重试", "error", null);
        }
    }
    private async Task WriteResponse(HttpContext context, string errorMsg, string errorType, object data)
    {
        // 兼容原MVC体系的习惯:业务错误也返回200状态码,前端通过响应字段判断错误
        context.Response.StatusCode = StatusCodes.Status200OK;
        context.Response.ContentType = "application/json";
        var response = new ApiResponse
        {
            ErrorMsg = errorMsg,
            ErrorType = errorType,
            Data = data
        };
        await context.Response.WriteAsJsonAsync(response);
    }
}

第三步:注册中间件与定制模型验证响应

在Startup.cs中配置管道顺序(全局异常中间件必须放在所有路由、MVC中间件之前):

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他前置中间件(如静态文件、路由匹配)
    app.UseMiddleware<GlobalExceptionMiddleware>();
    // 后续中间件(授权、MVC端点映射)
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints => endpoints.MapControllers());
}

针对.NET Core WebApi默认的模型验证响应(返回400状态码和ProblemDetails结构),定制为和全局异常完全一致的返回格式,覆盖“适配验证错误”场景:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers()
        .ConfigureApiBehaviorOptions(options =>
        {
            // 模型验证失败时,只返回第一条错误信息,符合“每次单条错误”的原有规则
            options.InvalidModelStateResponseFactory = context =>
            {
                var firstErrorMessage = context.ModelState.Values
                    .SelectMany(v => v.Errors)
                    .FirstOrDefault()?.ErrorMessage ?? "参数校验失败";
                return new OkObjectResult(new ApiResponse
                {
                    ErrorMsg = firstErrorMessage,
                    ErrorType = "warning",
                    Data = null
                });
            };
        });
}

完成配置后,所有场景的错误返回逻辑完全统一:

  • 手动抛出的ApiErrorException
  • 模型参数校验失败
  • 未捕获的系统异常
    都会返回结构完全一致的JSON,不需要在任何接口内写try-catch逻辑。

简化正常业务返回的辅助方法

不需要写复杂的泛型约束来适配BaseDataObject派生类,只需要简单的静态辅助方法统一包装正常返回的业务数据:

public static class ApiResponseHelper
{
    /// <summary>
    /// 成功返回任意类型的业务数据(单对象/列表/空值均可)
    /// </summary>
    public static ApiResponse Success<T>(T data)
    {
        return new ApiResponse
        {
            ErrorMsg = null,
            ErrorType = null,
            Data = data
        };
    }
}

Controller层的Action写法示例,不管返回单条还是列表,结构完全一致:

// 返回单条业务对象
[HttpGet("single")]
public async Task<ApiResponse> GetSingleAdData(int id)
{
    var data = await _studentService.GetAdDataById(id);
    return ApiResponseHelper.Success(data);
}

// 返回业务对象列表
[HttpGet("list")]
public async Task<ApiResponse> GetAllAdData()
{
    var data = await _studentService.GetAllAdDataList();
    return ApiResponseHelper.Success(data);
}

方案对比

  • 对比原有让所有业务类继承BaseDataObject的方案:不需要修改任何现有业务类的继承关系,没有冗余的序列化字段,单条/列表返回格式完全统一,前端不需要做分支判断
  • 对比每个接口手写catch的方案:全局异常处理接管所有错误逻辑,没有重复代码,不会出现漏写try-catch导致的错误响应格式不统一的问题
  • 对比之前尝试的泛型约束方案:不需要对业务类做任何继承限制,泛型辅助方法没有额外类型约束,适配所有场景

内容的提问来源于stack exchange,提问作者Eric Brown - Cal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.29 01:57:03