.NET 7 Web API能否在中间件中提前访问控制器的反序列化对象?
直接在自定义中间件里获取控制器即将接收的已反序列化对象是做不到的——因为ASP.NET Core的模型绑定(包括[FromBody]的反序列化操作)是在中间件管道执行完毕后、控制器方法执行前的模型绑定阶段完成的。中间件运行时,请求体还只是原始数据流,模型绑定还没启动,自然拿不到已反序列化的对象;如果强行在中间件里读取请求体并反序列化,后续模型绑定阶段会因为数据流已被读取而失败,就算缓存数据流,也会导致两次反序列化,违背你要保证性能的需求。
不过有几个更合适的方案,既能实现共享逻辑的统一处理,又不会重复反序列化:
1. 使用异步Action过滤器(推荐)
Action过滤器的执行时机刚好在模型绑定完成后、控制器方法执行前,此时ActionArguments里已经是反序列化好的参数对象,直接拿来用就行。
示例代码:
public class BaseRequestFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { // 遍历所有控制器方法参数,找到继承自BaseRequest的实例 foreach (var arg in context.ActionArguments.Values) { if (arg is BaseRequest baseReq) { // 执行你的共享逻辑,比如参数校验、日志记录、字段赋值等 baseReq.Timestamp = DateTime.UtcNow; ValidateBaseRequest(baseReq, context); } } // 继续执行后续过滤器和控制器方法 await next(); } private void ValidateBaseRequest(BaseRequest request, ActionExecutingContext context) { // 这里写你的共享校验逻辑 if (request.Id <= 0) { context.ModelState.AddModelError(nameof(request.Id), "ID必须大于0"); } } }
然后在Program.cs里注册过滤器:
builder.Services.AddControllers(options => { options.Filters.Add<BaseRequestFilter>(); });
这个方案无需修改现有控制器方法,只要参数继承自BaseRequest,就会自动触发共享逻辑,完全符合DRY原则。
2. 自定义模型绑定器
如果需要在反序列化过程中就介入处理(比如自定义反序列化规则+共享逻辑),可以给基类BaseRequest写一个自定义模型绑定器,绑定过程中完成反序列化和共享逻辑,确保只做一次反序列化。
示例代码:
public class BaseRequestModelBinder : IModelBinder { private readonly JsonOptions _jsonOptions; public BaseRequestModelBinder(IOptions<JsonOptions> jsonOptions) { _jsonOptions = jsonOptions.Value; } public async Task BindModelAsync(ModelBindingContext bindingContext) { var request = bindingContext.HttpContext.Request; using var reader = new StreamReader(request.Body); var json = await reader.ReadToEndAsync(); // 反序列化到目标类型 var model = JsonSerializer.Deserialize(json, bindingContext.ModelType, _jsonOptions.JsonSerializerOptions); if (model is BaseRequest baseReq) { // 执行共享逻辑 baseReq.Timestamp = DateTime.UtcNow; ValidateBaseRequest(baseReq, bindingContext); } bindingContext.Result = ModelBindingResult.Success(model); } private void ValidateBaseRequest(BaseRequest request, ModelBindingContext bindingContext) { // 共享校验逻辑 if (string.IsNullOrEmpty(request.RequestId)) { request.RequestId = Guid.NewGuid().ToString(); } } }
然后给基类标记模型绑定器:
[ModelBinder(typeof(BaseRequestModelBinder))] public class BaseRequest { public string RequestId { get; set; } public DateTime Timestamp { get; set; } // 其他共享属性 }
这个方案适合需要自定义反序列化行为的场景,同样只做一次反序列化。
3. 控制器基类
如果所有涉及的控制器都继承同一个基类,可以在基类的OnActionExecuting方法里处理共享逻辑,本质和Action过滤器类似,但作用范围仅限继承该基类的控制器:
public class BaseApiController : ControllerBase { public override void OnActionExecuting(ActionExecutingContext context) { foreach (var arg in context.ActionArguments.Values) { if (arg is BaseRequest baseReq) { // 执行共享逻辑 baseReq.Timestamp = DateTime.UtcNow; } } base.OnActionExecuting(context); } }
然后让你的业务控制器继承BaseApiController即可。
内容的提问来源于stack exchange,提问作者Wolf Donner

