在 Asp.Net Core 上为 MongoDB ObjectId 创建 ModelBinder
Creating a ModelBinder for MongoDB ObjectId on Asp.Net Core
我正在尝试为我的模型中的 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;
}
}
这是 class 我正在尝试将 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 方法,但是我似乎无法从 post 数据中获取 Id 参数值。我可以看到 bindingContext.FieldName 是正确的;它设置为 Id 但 result.FirstValue 为空。
我已经离开 Asp.Net MVC 一段时间了,似乎很多东西都发生了变化,变得更加混乱:-)
编辑
根据评论,我认为我应该提供更多背景信息。
如果我将 [FromBody] 放在 Player 操作参数之前,player 将设置为 null。如果我删除 [FromBody],player 将设置为默认值,而不是我 post 的值。 post body如下所示,只是一个简单的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]
时,模型绑定过程将默认使用路由、查询字符串和表单值来填充 Player
的属性。在您的示例中,这些位置没有此类属性,因此设置了 Player
属性中的 none。
If I put [FromBody] before the Player action parameter, player is set to null.
由于存在 [FromBody]
属性,模型绑定进程会尝试根据随请求提供的 Content-Type
从正文中读取。如果这是 application/json
,正文将被解析为 JSON 并映射到您的 Player
的属性。在您的示例中,JSON-解析过程失败,因为它不知道如何从 string
转换为 ObjectId
。发生这种情况时,您控制器中的 ModelState.IsValid
将 return 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 的形式从正文中加入模型绑定,这就是模型绑定在此停止的地方情景.
我在上面提到,由于不了解如何处理 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());
);
你在 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;
}
}
我正在尝试为我的模型中的 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;
}
}
这是 class 我正在尝试将 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 方法,但是我似乎无法从 post 数据中获取 Id 参数值。我可以看到 bindingContext.FieldName 是正确的;它设置为 Id 但 result.FirstValue 为空。
我已经离开 Asp.Net MVC 一段时间了,似乎很多东西都发生了变化,变得更加混乱:-)
编辑 根据评论,我认为我应该提供更多背景信息。
如果我将 [FromBody] 放在 Player 操作参数之前,player 将设置为 null。如果我删除 [FromBody],player 将设置为默认值,而不是我 post 的值。 post body如下所示,只是一个简单的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]
时,模型绑定过程将默认使用路由、查询字符串和表单值来填充 Player
的属性。在您的示例中,这些位置没有此类属性,因此设置了 Player
属性中的 none。
If I put [FromBody] before the Player action parameter, player is set to null.
由于存在 [FromBody]
属性,模型绑定进程会尝试根据随请求提供的 Content-Type
从正文中读取。如果这是 application/json
,正文将被解析为 JSON 并映射到您的 Player
的属性。在您的示例中,JSON-解析过程失败,因为它不知道如何从 string
转换为 ObjectId
。发生这种情况时,您控制器中的 ModelState.IsValid
将 return 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 的形式从正文中加入模型绑定,这就是模型绑定在此停止的地方情景.
我在上面提到,由于不了解如何处理 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());
);
你在 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;
}
}