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

如何在C#中通过中间件重写HTTP响应体实现响应标准化

统一API响应格式中间件实现方案

需求背景

已实现一个可拦截API所有请求的中间件,目标是当请求返回HttpStatus.Ok或HttpStatus.BadRequest时,返回统一格式的响应,当前中间件仅能原样转发响应。

标准化响应格式示例

{"results": 0,"numberOfRows": 1,"requestGuid": "xxx","errors": [],"StatusCode": 200,"environment": "LOC"}

现有问题

当API端点收到缺少必填属性的请求时,会返回默认400响应:

{"errors": {"MyProp": ["The MyProp field is required."]},"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1","title": "One or more validation errors occurred.","status": 400,"traceId": "xxx"}

需要将这类响应转换为自定义标准化格式,当前中间件的400分支逻辑未实现。

解决方案实现

完整中间件代码

using System.Text;
using System.Text.Json;

public async Task InvokeAsync(HttpContext context)
{
    try
    {
        Stream originalBodyStream = context.Response.Body;
        await using (MemoryStream memoryStream = new())
        {
            context.Response.Body = memoryStream;

            await _next(context);

            // 处理200和400响应的标准化逻辑
            if (context.Response.StatusCode == StatusCodes.Status200OK || 
                context.Response.StatusCode == StatusCodes.Status400BadRequest)
            {
                // 重置流位置,读取原始响应内容
                memoryStream.Seek(0, SeekOrigin.Begin);
                string originalResponse = await new StreamReader(memoryStream).ReadToEndAsync();

                // 构建标准化响应对象
                var standardResponse = new StandardResponse
                {
                    RequestGuid = context.TraceIdentifier,
                    Environment = "LOC",
                    NumberOfRows = 1,
                    Results = 0
                };

                if (context.Response.StatusCode == StatusCodes.Status400BadRequest)
                {
                    standardResponse.StatusCode = HttpStatusCode.BadRequest;
                    // 解析原始400响应中的错误信息
                    if (!string.IsNullOrEmpty(originalResponse))
                    {
                        var validationError = JsonSerializer.Deserialize<DefaultValidationError>(originalResponse);
                        if (validationError?.Errors != null)
                        {
                            standardResponse.Errors = validationError.Errors
                                .SelectMany(kv => kv.Value.Select(msg => new ErrorsModel { ErrorDetail = $"{kv.Key}: {msg}" }))
                                .ToList();
                        }
                        else
                        {
                            standardResponse.Errors = new List<ErrorsModel> { new() { ErrorDetail = "请求参数验证失败" } };
                        }
                    }
                }
                else
                {
                    standardResponse.StatusCode = HttpStatusCode.OK;
                    // 如需保留原始响应的业务数据,可在此处解析originalResponse并赋值给Results等字段
                }

                // 重置响应,写入标准化内容
                context.Response.Body = originalBodyStream;
                context.Response.ContentType = "application/json";
                context.Response.StatusCode = (int)standardResponse.StatusCode;

                var json = JsonSerializer.Serialize(standardResponse);
                await context.Response.WriteAsync(json, Encoding.UTF8);
            }
            else
            {
                // 其他状态码原样转发
                memoryStream.Seek(0, SeekOrigin.Begin);
                context.Response.Body = originalBodyStream;
                await memoryStream.CopyToAsync(originalBodyStream);
            }
        }
    }
    catch (Exception exception)
    {
        // 异常处理:返回标准化500错误响应
        context.Response.StatusCode = StatusCodes.Status500InternalServerError;
        var errorResponse = new StandardResponse
        {
            StatusCode = HttpStatusCode.InternalServerError,
            RequestGuid = context.TraceIdentifier,
            Environment = "LOC",
            Errors = new List<ErrorsModel> { new() { ErrorDetail = exception.Message } },
            Results = 0,
            NumberOfRows = 1
        };
        var json = JsonSerializer.Serialize(errorResponse);
        await context.Response.WriteAsync(json, Encoding.UTF8);
    }
}

// 标准化响应模型
public class StandardResponse
{
    public int Results { get; set; }
    public int NumberOfRows { get; set; }
    public string RequestGuid { get; set; }
    public List<ErrorsModel> Errors { get; set; } = new();
    public HttpStatusCode StatusCode { get; set; }
    public string Environment { get; set; }
}

// 错误详情模型
public class ErrorsModel
{
    public string ErrorDetail { get; set; }
}

// 用于解析默认400验证错误的模型
public class DefaultValidationError
{
    public Dictionary<string, List<string>> Errors { get; set; }
}

关键逻辑说明

  1. 捕获原始响应:通过MemoryStream替换响应流,捕获后续中间件或接口返回的原始响应内容。
  2. 解析错误信息:针对400响应,反序列化原始验证错误,将字段级错误转换为标准化的错误详情格式。
  3. 构建统一响应:统一填充requestGuid(使用请求的TraceId)、environment字段,根据状态码设置StatusCode和错误列表。
  4. 重置响应输出:将响应流切换回原始流,设置正确的Content-Type,写入标准化JSON内容。
  5. 异常兜底处理:捕获中间件执行过程中的异常,返回标准化的500错误响应。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 22:09:24