我正在尝试为我的模型中的 ObjectId 类型创建一个非常简单的模型绑定(bind)器,但到目前为止似乎无法让它工作。
这是模型 Binder :
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 参数绑定(bind)到的类:
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; }
}
这是 Action 方法:
[HttpPost("join")]
public async Task<SomeThing> Join(Player player)
{
return await _someService.DoSomethingOnthePlayer(player);
}
为了让这段代码工作,我的意思是为了让模型绑定(bind)器运行,我从 Controller 继承了 Controller 并从 Player 参数中删除了 [FromBody]
属性。
当我运行它时,我可以进入模型联编程序的 BindModelAsync 方法,但是我似乎无法从发布数据中获取 Id 参数值。我可以看到 bindingContext.FieldName 是正确的;它设置为 Id 但 result.FirstValue 为空。
我已经离开 Asp.Net MVC 一段时间了,似乎很多东西都发生了变化,变得更加困惑:-)
编辑 根据评论,我认为我应该提供更多背景信息。
如果我将 [FromBody] 放在 Player 操作参数之前,player 将设置为 null。如果我删除 [FromBody],player 将设置为默认值,而不是我发布的值。帖子正文如下所示,它只是一个简单的 JSON:
{
"Id": "507f1f77bcf86cd799439011"
"PlatformId": "9c8aae0f-6aad-45df-a5cf-4ca8f729b70f"
}
最佳答案
If I remove [FromBody], player is set to a default value, not to the values I post.
从正文中读取数据是选择加入(除非您使用 [ApiController]
)。当您从 Player
参数中删除 [FromBody]
时,模型绑定(bind)过程将使用路由查询填充 Player
的属性,查询-默认情况下,字符串和表单值。在您的示例中,这些位置没有此类属性,因此未设置 Player
的任何属性。
If I put [FromBody] before the Player action parameter, player is set to null.
由于存在 [FromBody]
属性,模型绑定(bind)进程会尝试根据随请求提供的 Content-Type
从正文中读取内容。如果这是 application/json
,正文将被解析为 JSON 并映射到您的 Player
的属性。在您的示例中,JSON 解析过程失败,因为它不知道如何将 string
转换为 ObjectId
。发生这种情况时, Controller 中的 ModelState.IsValid
将返回 false
并且您的 Player
参数将为 null
。
For this code to work, I mean for the model binder to run, I inherited the controller from Controller and removed the [FromBody] attribute from the Player parameter.
当您删除 [FromBody]
时,您在 Id
属性上设置的 [ModelBinder(...)]
属性是受到尊重,因此您的代码可以运行。然而,由于 [FromBody
] 的存在,这个属性实际上被忽略了。这里的幕后发生了很多事情,但本质上归结为这样一个事实,即您已经选择以 JSON 形式从正文中进行模型绑定(bind),这就是模型绑定(bind)在这种情况下停止的地方。
我在上面提到,由于不了解如何处理 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());
);
关于c# - 在 Asp.Net Core 上为 MongoDB ObjectId 创建一个 ModelBinder,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53246755/