[英]Creating a ModelBinder for MongoDB ObjectId on Asp.Net Core
I'm trying to create a very simple model binder for ObjectId types in my models but can't seem to make it work so far. 我正在尝试为我的模型中的ObjectId类型创建一个非常简单的模型绑定程序,但到目前为止似乎还无法使它工作。
Here's the model binder: 这是模型资料夹:
public class ObjectIdModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var result = bindingContext.ValueProvider.GetValue(bindingContext.FieldName);
return Task.FromResult(new ObjectId(result.FirstValue));
}
}
This is the ModelBinderProvider I've coded: 这是我编写的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;
}
}
Here's the class I'm trying to bind the body parameter to: 这是我尝试将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; }
}
This is the action method: 这是操作方法:
[HttpPost("join")]
public async Task<SomeThing> Join(Player player)
{
return await _someService.DoSomethingOnthePlayer(player);
}
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. 为了使此代码正常工作,我的意思是要运行模型绑定程序,我从Controller继承了控制器,并从Player参数中删除了[FromBody]
属性。
When I run this, I can step into BindModelAsync method of the model binder, however I can't seem to get the Id parameter value from the post data. 运行此命令时,可以进入模型绑定程序的BindModelAsync方法,但是似乎无法从发布数据中获取Id参数值。 I can see the bindingContext.FieldName is correct; 我可以看到bindingContext.FieldName是正确的; it is set to Id but result.FirstValue is null. 它设置为Id,但result.FirstValue为null。
I've been away from Asp.Net MVC for a while, and it seems lots of things have been changed and became more confusing :-) 我离开Asp.Net MVC已有一段时间了,看来很多事情已经改变并且变得更加混乱:-)
EDIT Based on comments I think I should provide more context. 编辑基于评论,我认为我应该提供更多的背景信息。
If I put [FromBody] before the Player action parameter, player is set to null. 如果我把[FromBody]玩家操作参数之前, 播放器设置为null。 If I remove [FromBody], player is set to a default value, not to the values I post. 如果删除[FromBody],则播放器将设置为默认值,而不是我发布的值。 The post body is shown below, it's just a simple JSON: 帖子正文如下所示,它只是一个简单的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. 如果删除[FromBody],则播放器将设置为默认值,而不是我发布的值。
Reading data from the body is opt-in (unless you're using [ApiController]
). 从主体读取数据是可选的 (除非您使用[ApiController]
)。 When you remove [FromBody]
from your Player
parameter, the model-binding process will look to populate properties of Player
using the route, query-string and form-values, by default. 当从Player
参数中删除[FromBody]
,默认情况下,模型绑定过程将使用路由,查询字符串和表单值来填充Player
属性。 In your example, there are no such properties in these locations and so none of Player
's properties get set. 在您的示例中,这些位置没有此类属性,因此没有设置Player
的属性。
If I put [FromBody] before the Player action parameter, player is set to null. 如果我把[FromBody]玩家操作参数之前, 播放器设置为null。
With the presence of the [FromBody]
attribute, the model-binding process attempts to read from the body according to the Content-Type
provided with the request. 在[FromBody]
属性存在的情况下,模型绑定过程会根据请求所提供的Content-Type
尝试从主体读取Content-Type
。 If this is application/json
, the body will be parsed as JSON and mapped to your Player
's properties. 如果这是application/json
,则正文将解析为JSON并映射到Player
的属性。 In your example, the JSON-parsing process fails as it doesn't know how to convert from a string
to an ObjectId
. 在您的示例中,JSON解析过程失败,因为它不知道如何从string
转换为ObjectId
。 When this happens, ModelState.IsValid
within your controller will return false
and your Player
parameter will be null
. 发生这种情况时,控制器内的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. 为了使此代码正常工作,我的意思是要运行模型绑定程序,我从Controller继承了控制器,并从Player参数中删除了[FromBody]属性。
When you remove [FromBody]
, the [ModelBinder(...)]
attribute you've set on your Id
property is respected and so your code runs. 删除[FromBody]
,将尊重在Id
属性上设置的[ModelBinder(...)]
属性,因此代码将运行。 However, with the presence of [FromBody
], this attribute effectively is ignored. 但是,如果存在[FromBody
],则实际上将忽略此属性。 There's a lot going on behind-the-scenes here, but essentially it boils down to the fact that you've already opted-in to model-binding from the body as JSON and that's where model-binding stops in this scenario. 幕后有很多事情要做,但本质上可以归结为以下事实:您已经选择将主体的模型绑定作为JSON,并且在这种情况下,模型绑定就停止了。
I mentioned above that it's the JSON-parsing process that's failing here due to not understanding how to process ObjectId
. 上面我提到过,由于不了解如何处理ObjectId
导致JSON解析过程失败了。 As this JSON-parsing is handled by Newtonsoft.Json (aka JSON.NET), a possible solution is to create a custom JsonConverter
. 由于此JSON解析由Newtonsoft.Json(又名JSON.NET)处理,因此可能的解决方案是创建自定义JsonConverter
。 This is covered well here on Stack Overflow, so I won't go into the details of how it works. 这在Stack Overflow上已经很好地介绍了,因此我将不详细介绍其工作原理。 Here's a complete example (error-handling omitted for brevity and laziness): 这是一个完整的示例(为简洁起见,省略了错误处理):
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());
}
To make use of this, just replace your existing [ModelBinder(...)]
attribute with a [JsonConverter(...)]
attribute, like this: 要使用此功能,只需将现有的[ModelBinder(...)]
属性替换为[JsonConverter(...)]
属性,如下所示:
[BsonId]
[JsonConverter(typeof(ObjectIdJsonConverter))]
public ObjectId Id { get; set; }
Alternatively, you can register ObjectIdJsonConverter
globally so that it applies to all ObjectId
properties, using something like this in Startup.ConfigureServices
: 或者,您可以在Startup.ConfigureServices
使用类似的方法全局注册ObjectIdJsonConverter
,以便将其应用于所有ObjectId
属性:
services.AddMvc()
.AddJsonOptions(options =>
options.SerializerSettings.Converters.Add(new ObjectIdJsonConverter());
);
You were mistaken at ModelBinder. 您被误认为是ModelBinder。 Correct code: 正确的代码:
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;
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.