在Asp.Net Core上为MongoDB ObjectId创建ModelBinder [英] Creating a ModelBinder for MongoDB ObjectId on Asp.Net Core

查看:66
本文介绍了在Asp.Net Core上为MongoDB ObjectId创建ModelBinder的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试为模型中的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已有一段时间了,看来很多事情已经改变并且变得更加混乱:-)

编辑 根据评论,我认为我应该提供更多背景信息.

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

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

解决方案

如果我删除了[FromBody],则 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.

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. I can see the bindingContext.FieldName is correct; it is set to Id but result.FirstValue is 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 :-)

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. If I remove [FromBody], player is set to a default value, not to the values I post. The post body is shown below, it's just a simple 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.

Reading data from the body is opt-in (unless you're using [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. In your example, there are no such properties in these locations and so none of Player's properties get set.

If I put [FromBody] before the Player action parameter, player is set to 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. If this is application/json, the body will be parsed as JSON and mapped to your Player's properties. In your example, the JSON-parsing process fails as it doesn't know how to convert from a string to an ObjectId. When this happens, ModelState.IsValid within your controller will return false and your Player parameter will be 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.

When you remove [FromBody], the [ModelBinder(...)] attribute you've set on your Id property is respected and so your code runs. However, with the presence of [FromBody], this attribute effectively is ignored. 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.


I mentioned above that it's the JSON-parsing process that's failing here due to not understanding how to process ObjectId. As this JSON-parsing is handled by Newtonsoft.Json (aka JSON.NET), a possible solution is to create a custom JsonConverter. This is covered well here on Stack Overflow, so I won't go into the details of how it works. 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:

[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:

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

这篇关于在Asp.Net Core上为MongoDB ObjectId创建ModelBinder的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆