仅含POST方法的API输入验证中间件开发方案问询
基于中间件实现POST API的前置输入校验方案
我最近刚好搞定过一模一样的需求——所有API都是POST动作,要在请求到控制器之前就完成输入校验,用中间件确实是最适合的方案。结合你给出的代码框架,我把完整的实现逻辑补全了,还加了很多实用细节:
核心实现代码
using Microsoft.AspNetCore.Http; using System.Text.Json; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.Controllers; public class ValidateInputMiddleware { private readonly RequestDelegate _next; private readonly JsonSerializerOptions _jsonSerializerOptions; public ValidateInputMiddleware(RequestDelegate next) { _next = next; // 这里的JSON配置要和你项目里的保持一致,避免序列化/反序列化出现问题 _jsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, WriteIndented = false }; } public async Task Invoke(HttpContext httpContext) { // 只处理POST请求,其他请求直接放行 if (httpContext.Request.Method.Equals("POST", StringComparison.OrdinalIgnoreCase)) { // 关键:请求体流默认只能读一次,必须开启缓冲并重置位置,不然后续控制器就拿不到数据了 httpContext.Request.EnableBuffering(); var requestBodyContent = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); httpContext.Request.Body.Position = 0; try { // 通过端点元数据动态获取当前请求对应的输入模型类型,不用硬编码,通用性拉满 var endpoint = httpContext.GetEndpoint(); if (endpoint != null) { var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>(); if (actionDescriptor != null && actionDescriptor.Parameters.Any()) { var inputModelType = actionDescriptor.Parameters.First().ParameterType; // 把JSON请求体反序列化为输入模型 var inputModel = JsonSerializer.Deserialize(requestBodyContent, inputModelType, _jsonSerializerOptions); // 用.NET自带的数据注解做基础验证,要是需要复杂规则可以换成FluentValidation var validationContext = new ValidationContext(inputModel); var validationErrors = new List<ValidationResult>(); bool isInputValid = Validator.TryValidateObject(inputModel, validationContext, validationErrors, validateAllProperties: true); if (!isInputValid) { // 验证失败直接返回400错误,把错误字段和信息返回给前端 var errorDetails = validationErrors.Select(error => new { Field = error.MemberNames.FirstOrDefault(), Message = error.ErrorMessage }); httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsJsonAsync(new { Errors = errorDetails }, _jsonSerializerOptions); return; // 终止请求管道,不再往下走 } // 验证成功,把模型存到HttpContext.Items里,控制器可以直接拿,不用重复反序列化 httpContext.Items["ValidatedInputModel"] = inputModel; } } } catch (JsonException jsonEx) { // 处理JSON格式错误的情况 httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; httpContext.Response.ContentType = "application/json"; await httpContext.Response.WriteAsJsonAsync(new { Error = "请求格式错误", Details = jsonEx.Message }, _jsonSerializerOptions); return; } } // 非POST请求或者验证通过,继续执行下一个中间件 await _next(httpContext); } } // 写个扩展方法,注册中间件的时候更简洁 public static class ValidateInputMiddlewareExtensions { public static IApplicationBuilder UseInputValidation(this IApplicationBuilder app) { return app.UseMiddleware<ValidateInputMiddleware>(); } }
关键细节说明
- 请求体复用处理:一定要调用
EnableBuffering()并重置流位置,这是很多人踩坑的点——如果不这么做,控制器里读取请求体的时候会是空的。 - 动态匹配模型:通过端点元数据获取控制器动作的参数类型,不用为每个API写单独的验证逻辑,一套中间件搞定所有POST请求。
- 验证逻辑扩展:我用的是.NET自带的
Validator,如果你的验证规则比较复杂(比如依赖数据库、多字段联动),可以换成FluentValidation,只需要把验证部分替换成var validationResult = _validator.Validate(inputModel);就行,记得把验证器通过依赖注入进来。 - 错误响应规范:验证失败返回400 Bad Request,同时返回具体的错误字段和信息,前端能直接用来提示用户,符合REST API的设计规范。
- 模型传递优化:验证成功后把模型存到
HttpContext.Items,控制器里可以直接var model = (YourInputModel)HttpContext.Items["ValidatedInputModel"];,不用再重复反序列化请求体,提升性能。
注册中间件
最后记得在Program.cs里注册这个中间件,要放在路由中间件之前:
var app = builder.Build(); // 其他中间件... app.UseInputValidation(); // 注册输入验证中间件 app.UseRouting(); // 其他中间件...
内容的提问来源于stack exchange,提问作者arpymastro
相关产品推荐
相关产品推荐

