.NET Core 3.1 Web API如何绑定路由参数与请求体字段到同一模型
解决方案
方案1:拆分参数手动赋值(最推荐,无侵入,适配多场景)
该方案改动最小,完全不需要修改UpdateUserCommand模型代码,不影响模型其他使用场景:
[HttpPut("{id}")] [ProducesResponseType(typeof(UpdateUserCommandResponse), StatusCodes.Status200OK)] public async Task<ActionResult<UpdateUserCommandResponse>> PutUser( // 单独接收路由中的id参数 int id, // 明确指定模型从Json请求体绑定 [FromBody] UpdateUserCommand updateUserCommand) { // 手动将路由id赋值给模型 updateUserCommand.Id = id; var response = await _mediator.Send(updateUserCommand); return Ok(response); }
UpdateUserCommand保持无特性的干净状态即可:
public class UpdateUserCommand : IRequest<UpdateUserCommandResponse> { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
方案2:自定义模型绑定器(适合批量同类型需求)
如果有大量同规则的接口需要实现「路由取Id+Body取其他字段」的绑定逻辑,可以自定义模型绑定器避免重复手动赋值:
- 自定义绑定器实现
public class RouteIdAndBodyBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); // 1. 从路由读取Id var routeIdResult = bindingContext.ValueProvider.GetValue("id"); if (routeIdResult == ValueProviderResult.None || !int.TryParse(routeIdResult.FirstValue, out var id)) { bindingContext.ModelState.TryAddModelError("id", "路由参数Id非法"); bindingContext.Result = ModelBindingResult.Failed(); return; } // 2. 从请求体读取剩余字段 bindingContext.HttpContext.Request.EnableBuffering(); using var reader = new StreamReader(bindingContext.HttpContext.Request.Body, leaveOpen: true); var bodyJson = await reader.ReadToEndAsync(); bindingContext.HttpContext.Request.Body.Position = 0; var model = JsonSerializer.Deserialize(bodyJson, bindingContext.ModelType, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); // 3. 赋值路由Id到模型 var idProp = bindingContext.ModelType.GetProperty("Id"); idProp?.SetValue(model, id); bindingContext.Result = ModelBindingResult.Success(model); } }
- 控制器参数指定绑定器
[HttpPut("{id}")] [ProducesResponseType(typeof(UpdateUserCommandResponse), StatusCodes.Status200OK)] public async Task<ActionResult<UpdateUserCommandResponse>> PutUser( [ModelBinder(BinderType = typeof(RouteIdAndBodyBinder))] UpdateUserCommand updateUserCommand) { var response = await _mediator.Send(updateUserCommand); return Ok(response); }
注意事项
你之前调整代码时添加的[FromForm]特性仅适配表单格式的请求,如果你需要接收Json请求体,应该使用[FromBody]特性。默认情况下.NET Core Web API复杂类型参数会优先从请求体读取,不会自动合并路由中的同名字段,因此会出现Id始终为0的问题。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

