【问题标题】:Creating a ModelBinder for MongoDB ObjectId on Asp.Net Core在 Asp.Net Core 上为 MongoDB ObjectId 创建 ModelBinder
【发布时间】:2019-04-14 06:20:58
【问题描述】:

我正在尝试为我的模型中的 ObjectId 类型创建一个非常简单的模型绑定器,但目前似乎无法使其工作。

这是模型绑定器:

public class ObjectIdModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var result = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
        return Task.FromResult(new ObjectId(result.FirstValue));
    }
}

这是我编写的 ModelBinderProvider:

public class ObjectIdModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null) throw new ArgumentNullException(nameof(context));

        if (context.Metadata.ModelType == typeof(ObjectId))
        {
            return new BinderTypeModelBinder(typeof(ObjectIdModelBinder));
        }

        return null;
    }
}

这是我尝试将 body 参数绑定到的类:

public class Player
{
    [BsonId]
    [ModelBinder(BinderType = typeof(ObjectIdModelBinder))]
    public ObjectId Id { get; set; }
    public Guid PlatformId { get; set; }
    public string Name { get; set; }
    public int Score { get; set; }
    public int Level { get; set; }
}

这是动作方法:

[HttpPost("join")]
public async Task<SomeThing> Join(Player player)
{
    return await _someService.DoSomethingOnthePlayer(player);
}

为了让这段代码工作,我的意思是为了让模型绑定器运行,我从 Controller 继承了控制器,并从 Player 参数中删除了 [FromBody] 属性。

当我运行它时,我可以进入模型绑定器的 BindModelAsync 方法,但是我似乎无法从发布数据中获取 Id 参数值。我可以看到 bindingContext.FieldName 是正确的;它设置为 Id 但 result.FirstValue 为空。

我已经离开 Asp.Net MVC 一段时间了,似乎很多事情都发生了变化,变得更加混乱 :-)

编辑 基于 cmets,我认为我应该提供更多上下文。

如果我将 [FromBody] 放在 Player 操作参数之前,player 将设置为 null。如果我删除 [FromBody],player 会设置为默认值,而不是我发布的值。帖子正文如下所示,只是一个简单的 JSON:

{
    "Id": "507f1f77bcf86cd799439011"
    "PlatformId": "9c8aae0f-6aad-45df-a5cf-4ca8f729b70f"
}

【问题讨论】:

  • 你是说var result = .. 是null?
  • 不,不是结果。但 result.FirstValue 为空。还编辑了问题。
  • 您确认Request.Form(或Request.Body)确实包含id的值吗?
  • 你传递什么作为输入?您也可以发布您的测试输入吗?

标签: c# mongodb asp.net-core asp.net-core-mvc model-binding


【解决方案1】:

你在 ModelBinder 上搞错了。正确代码:

public class ObjectIdModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var result = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);

        bindingContext.Result = ModelBindingResult.Success(new ObjectId(result.FirstValue));

        return Task.CompletedTask;
    }
}

【讨论】:

    【解决方案2】:

    如果我删除 [FromBody],player 会设置为默认值,而不是我发布的值。

    从正文中读取数据是选择加入(除非您使用的是[ApiController])。当您从 Player 参数中删除 [FromBody] 时,模型绑定过程将默认使用路由、查询字符串和表单值填充 Player 的属性。在您的示例中,这些位置没有此类属性,因此没有设置 Player 的任何属性。

    如果我将 [FromBody] 放在 Player 操作参数之前,player 将设置为 null。

    由于存在[FromBody] 属性,模型绑定过程会尝试根据请求提供的Content-Type 从正文中读取。如果这是 application/json,则正文将被解析为 JSON 并映射到您的 Player 的属性。在您的示例中,JSON 解析过程失败,因为它不知道如何将 string 转换为 ObjectId。发生这种情况时,控制器中的ModelState.IsValid 将返回false,而您的Player 参数将为null。

    为了让这段代码工作,我的意思是为了让模型绑定器运行,我从 Controller 继承了控制器,并从 Player 参数中删除了 [FromBody] 属性。

    当您删除 [FromBody] 时,您在 Id 属性上设置的 [ModelBinder(...)] 属性会受到尊重,因此您的代码会运行。但是,随着[FromBody] 的存在,这个属性实际上被忽略了。这里有很多幕后工作,但本质上归结为这样一个事实,即您已经选择从正文作为 JSON 进行模型绑定,这就是在这种情况下模型绑定停止的地方。


    我在上面提到,由于不了解如何处理ObjectId,此处失败的是 JSON 解析过程。由于此 JSON 解析由 Newtonsoft.Json(又名 JSON.NET)处理,因此一种可能的解决方案是创建自定义 JsonConverter。这在 Stack Overflow 上有很好的介绍,所以我不会详细介绍它的工作原理。。这是一个完整的例子(为了简洁和懒惰,省略了错误处理):

    public class ObjectIdJsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType) =>
            objectType == typeof(ObjectId);
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
            ObjectId.Parse(reader.Value as string);
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) =>
            writer.WriteValue(((ObjectId)value).ToString());
    }
    

    要使用此功能,只需将现有的 [ModelBinder(...)] 属性替换为 [JsonConverter(...)] 属性,如下所示:

    [BsonId]
    [JsonConverter(typeof(ObjectIdJsonConverter))]    
    public ObjectId Id { get; set; }
    

    或者,您可以全局注册ObjectIdJsonConverter,使其适用于所有ObjectId 属性,在Startup.ConfigureServices 中使用类似这样的内容:

    services.AddMvc()
            .AddJsonOptions(options =>
                options.SerializerSettings.Converters.Add(new ObjectIdJsonConverter());
            );
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-04
      • 2020-07-11
      • 2019-07-28
      • 1970-01-01
      • 1970-01-01
      • 2015-05-25
      相关资源
      最近更新 更多