'How do I force .net core 3.1 to deserialize all [FromBody] parameters with Newtonsoft instead of System.Text.Json?

I've done a fair bit of googling to resolve a break after converting an existing 2.1 solution to 3.1. The default Json serializer changed with the upgrade which shouldn't be a big deal, except I don't necessarily make my json parameter names the same as my properties in all cases. I've liberally used:

[JsonProperty( name = "ALegacyNamedList" , order = 0)]
public List<int> ABetterNamedList { get => aBetterNamedList ; private set => aBetterNamedList = value; }

because it's nice for obvious reasons, breaking existing pages not the least of which. I guess you see what's next; default-ly constructed objects all the way around, because System.Text.Json.Serialization needs a different attribute for a property:

[JsonPropertyName( "ALegacyNamedList" )]
public List<int> ABetterNamedList ...

So I could go through everywhere and find out where I've named Json parameters a bit different from the class properties for whatever reason. Or I could get my application to use Newtonsoft instead, right? There are numerous articles that spell it out.

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
...
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddNewtonsoftJson();
    services.AddControllers().AddNewtonsoftJson();
    ...
}

It took me an hour and 4 articles, and at least half a dozen related posts to get here, but I know it's not using Newtonsoft for the [FromBody] parameters of a controller:

[HttpPut("action")]
public string PutSomething( [FromBody] MyObject myObject )
{ ... }

where MyObject's property is attributed like

    List<int> aBetterNamedList = new List<SomeItems>();

    //[JsonPropertyName("ALegacyNamedList")]  // <-- This works / is not empty
    [JsonProperty("ALegacyNamedList", Order = 0)] // <-- This is default constructed / empty set because it newtonsoft is not used
    public List<int> ABetterNamedList { get => aBetterNamedList ; private set => aBetterNamedList = value; }`

So is there another place I need to update so that the pipeline will use the correct tool so that [FromBody] (application/json) content will be parsed not by System.Text but by Newtonsoft.Json?

[Edit] It was recommended that I clarify my post. I don't want to have to go through all of the code everywhere and change the Request [FromBody] individually, nor go through all of my code and update the working Newtonsoft attributes. The answer below allows me to change the model binding on a single call. I want the pipeline itself updated so that all bindings are done using newtonsoft deserialization.



Solution 1:[1]

I would recommend you to use this syntax to configure controllers

using Newtonsoft.Json.Serialization;
.....

public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews()
    .AddNewtonsoftJson(options =>
           options.SerializerSettings.ContractResolver =
              new CamelCasePropertyNamesContractResolver());
.....
}

Solution 2:[2]

A partial answer comes from this post: How to use the json.net... I don't like it because it only works on the [FromBody] tags that you specifically attribute to use the custom model binder. But it works fine. Just not a "total" solution, which as clarified at the end of my post would be to use Newtonsoft as the binder in the pipeline before it gets to the method.

/// <summary>
/// Custom model binder to be used when TrackableEntities coming in HttpPost Methods.
/// </summary>
public class TrackableEntityModelBinder : IModelBinder
{
    /// <inheritdoc/>
    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
        {
            var body = await reader.ReadToEndAsync().ConfigureAwait(continueOnCapturedContext: false);

            // Do something
            var value = JsonConvert.DeserializeObject(body, bindingContext.ModelType, new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
            });

            bindingContext.Result = ModelBindingResult.Success(value);
        }
    }
}

As mentioned, it does require updating every put/post where you want to use it:

[HttpPost]
public async Task<IActionResult> 
UpdateDati([ModelBinder(typeof(TrackableEntityModelBinder))] [FromBody] 
Vehicule model)
{
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Serge
Solution 2 Adam