简体   繁体   English

在Asp.Net Core上为MongoDB ObjectId创建ModelBinder

[英]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将返回falsePlayer参数将为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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM