'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 |
