.NET 6 API自定义模型绑定:含IDictionary的MediatR模型绑定失败
解决方案
1. 实现自定义模型绑定器
创建一个实现IModelBinder的绑定器,负责从路由提取Entity参数,并将请求体反序列化为IDictionary<string, object>赋值给Record属性:
using Microsoft.AspNetCore.Mvc.ModelBinding; using System.Text.Json; public class CreateSingleRecordModelBinder : IModelBinder { public async Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); // 从路由获取Entity参数 var entityValue = bindingContext.ValueProvider.GetValue("entity").FirstValue; if (string.IsNullOrEmpty(entityValue)) { bindingContext.ModelState.AddModelError(nameof(CreateSingleRecord.Entity), "Entity is required."); return; } // 重置请求体流位置,确保能读取完整内容 bindingContext.HttpContext.Request.Body.Position = 0; using var reader = new StreamReader(bindingContext.HttpContext.Request.Body); var bodyContent = await reader.ReadToEndAsync(); IDictionary<string, object>? record = null; if (!string.IsNullOrEmpty(bodyContent)) { try { record = JsonSerializer.Deserialize<IDictionary<string, object>>(bodyContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } catch (JsonException ex) { bindingContext.ModelState.AddModelError(nameof(CreateSingleRecord.Record), $"Invalid JSON body: {ex.Message}"); return; } } if (record == null) { bindingContext.ModelState.AddModelError(nameof(CreateSingleRecord.Record), "Record is required."); return; } // 构建并绑定模型实例 var model = new CreateSingleRecord { Entity = entityValue, Record = record }; bindingContext.Result = ModelBindingResult.Success(model); } }
2. 注册绑定器(两种方式二选一)
方式一:全局注册绑定器提供器
在Program.cs中添加全局绑定器配置,让系统自动识别CreateSingleRecord类型并使用自定义绑定器:
builder.Services.AddControllers(options => { options.ModelBinderProviders.Insert(0, new CreateSingleRecordModelBinderProvider()); }); // 配套的绑定器提供器类 public class CreateSingleRecordModelBinderProvider : IModelBinderProvider { public IModelBinder? GetBinder(ModelBinderProviderContext context) { if (context.Metadata.ModelType == typeof(CreateSingleRecord)) { return new BinderTypeModelBinder(typeof(CreateSingleRecordModelBinder)); } return null; } }
方式二:局部指定绑定器
直接在Action参数或模型类上标注绑定器,无需全局注册:
// 在Action参数上指定 public async Task<object> CreateSingleEntity([ModelBinder(BinderType = typeof(CreateSingleRecordModelBinder))] CreateSingleRecord model) { // process data } // 或者在模型类上标注 [ModelBinder(BinderType = typeof(CreateSingleRecordModelBinder))] public class CreateSingleRecord : ICommand<object> { public string Entity { get; init; } public IDictionary<string, object> Record { get; init; } }
3. 清理冗余属性
移除CreateSingleRecord类上原有的[FromRoute]和[FromBody]属性,避免与自定义绑定器逻辑冲突。
4. 测试验证
使用原请求测试,绑定逻辑会正常工作:
curl --location --request POST 'https://localhost:7299/api/data/cars' \ --header 'Accept: application/json' \ --header 'Content-Type: application/json' \ --data-raw '{ "model": 1, "name": "Ford", "id":"a47d52de-fcd1-48e7-8656-7edb84dc78bd", "is_created": true, "date":"2022-09-23", "datetime":"2022-09-23 13:10" }'
关键注意事项
- 重置请求体流位置:ASP.NET可能提前读取过请求流,重置
Position为0才能保证读取完整内容。 - JSON反序列化配置:开启
PropertyNameCaseInsensitive避免大小写不匹配导致的绑定失败。 - 错误处理:绑定过程中添加模型验证错误,符合ASP.NET原生的错误返回逻辑。
内容的提问来源于stack exchange,提问作者Misiu
相关产品推荐
相关产品推荐

