You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 17:10:14