Creating a ModelBinder for MongoDB ObjectId on Asp.Net Core











up vote
0
down vote

favorite
1












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.



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:



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:



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.



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"
}









share|improve this question
























  • Are you saying that var result = .. is null?
    – user3559349
    Nov 11 at 7:52










  • No, not the result. But result.FirstValue is null. Also edited the question.
    – Élodie Petit
    Nov 11 at 8:06










  • Have you confirmed that Request.Form (or Request.Body) does actually contain a value for id?
    – user3559349
    Nov 11 at 8:11












  • What do you pass as input? Can you post your test input as well?
    – Ajay
    Nov 11 at 8:18















up vote
0
down vote

favorite
1












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.



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:



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:



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.



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"
}









share|improve this question
























  • Are you saying that var result = .. is null?
    – user3559349
    Nov 11 at 7:52










  • No, not the result. But result.FirstValue is null. Also edited the question.
    – Élodie Petit
    Nov 11 at 8:06










  • Have you confirmed that Request.Form (or Request.Body) does actually contain a value for id?
    – user3559349
    Nov 11 at 8:11












  • What do you pass as input? Can you post your test input as well?
    – Ajay
    Nov 11 at 8:18













up vote
0
down vote

favorite
1









up vote
0
down vote

favorite
1






1





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.



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:



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:



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.



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"
}









share|improve this question















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.



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:



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:



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.



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"
}






c# asp.net-mvc asp.net-core asp.net-core-mvc






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 11 at 8:35

























asked Nov 11 at 7:40









Élodie Petit

2,36253158




2,36253158












  • Are you saying that var result = .. is null?
    – user3559349
    Nov 11 at 7:52










  • No, not the result. But result.FirstValue is null. Also edited the question.
    – Élodie Petit
    Nov 11 at 8:06










  • Have you confirmed that Request.Form (or Request.Body) does actually contain a value for id?
    – user3559349
    Nov 11 at 8:11












  • What do you pass as input? Can you post your test input as well?
    – Ajay
    Nov 11 at 8:18


















  • Are you saying that var result = .. is null?
    – user3559349
    Nov 11 at 7:52










  • No, not the result. But result.FirstValue is null. Also edited the question.
    – Élodie Petit
    Nov 11 at 8:06










  • Have you confirmed that Request.Form (or Request.Body) does actually contain a value for id?
    – user3559349
    Nov 11 at 8:11












  • What do you pass as input? Can you post your test input as well?
    – Ajay
    Nov 11 at 8:18
















Are you saying that var result = .. is null?
– user3559349
Nov 11 at 7:52




Are you saying that var result = .. is null?
– user3559349
Nov 11 at 7:52












No, not the result. But result.FirstValue is null. Also edited the question.
– Élodie Petit
Nov 11 at 8:06




No, not the result. But result.FirstValue is null. Also edited the question.
– Élodie Petit
Nov 11 at 8:06












Have you confirmed that Request.Form (or Request.Body) does actually contain a value for id?
– user3559349
Nov 11 at 8:11






Have you confirmed that Request.Form (or Request.Body) does actually contain a value for id?
– user3559349
Nov 11 at 8:11














What do you pass as input? Can you post your test input as well?
– Ajay
Nov 11 at 8:18




What do you pass as input? Can you post your test input as well?
– Ajay
Nov 11 at 8:18












1 Answer
1






active

oldest

votes

















up vote
1
down vote



accepted











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());
);





share|improve this answer





















    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53246755%2fcreating-a-modelbinder-for-mongodb-objectid-on-asp-net-core%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote



    accepted











    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());
    );





    share|improve this answer

























      up vote
      1
      down vote



      accepted











      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());
      );





      share|improve this answer























        up vote
        1
        down vote



        accepted







        up vote
        1
        down vote



        accepted







        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());
        );





        share|improve this answer













        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());
        );






        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Nov 14 at 15:04









        Kirk Larkin

        17.5k33653




        17.5k33653






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53246755%2fcreating-a-modelbinder-for-mongodb-objectid-on-asp-net-core%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Xamarin.iOS Cant Deploy on Iphone

            Glorious Revolution

            Dulmage-Mendelsohn matrix decomposition in Python