如何在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; } }
关键逻辑说明
- 捕获原始响应:通过
MemoryStream替换响应流,捕获后续中间件或接口返回的原始响应内容。 - 解析错误信息:针对400响应,反序列化原始验证错误,将字段级错误转换为标准化的错误详情格式。
- 构建统一响应:统一填充
requestGuid(使用请求的TraceId)、environment字段,根据状态码设置StatusCode和错误列表。 - 重置响应输出:将响应流切换回原始流,设置正确的Content-Type,写入标准化JSON内容。
- 异常兜底处理:捕获中间件执行过程中的异常,返回标准化的500错误响应。
内容的提问来源于stack exchange,提问作者Mlo
相关产品推荐
相关产品推荐

