ShouldSerialize method is not triggered in .NET Core 3 - c#

I normally use ShouldSerialize to exclude properties that have no data such as array but now, it does not appear to be triggered when I'm only using JSON serializer in .NET Core 3. It was being triggered when using NewtonSoft but I've removed it from my project since it no longer appears to be required.
For example:
private ICollection<UserDto> _users;
public ICollection<UserDto> Users
{
get => this._users ?? (this._users = new HashSet<UserDto>());
set => this._users = value;
}
public bool ShouldSerializeUsers()
{
return this._users?.Count > 0;
}
Any ideas why ShouldSerializeUsers is not being triggered?
I've seen other answers where you can use:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.NullValueHandling =
NullValueHandling.Ignore;
});
}
But I'd like to know if there is another way to handle this as I'm not using .AddMvc
Thanks.

The reason that your ShouldSerialize is not triggered in ASP.NET Core 3.0 is that, in this and subsequent versions of ASP.NET, a different JSON serializer is being used by default, namely System.Text.Json.JsonSerializer. See:
Try the new System.Text.Json APIs.
Breaking changes to Microsoft.AspNetCore.App in 3.0 #325.
The future of JSON in .NET Core 3.0 #90.
Unfortunately as of .NET Core 3.1 this serializer does not support the ShouldSerializeXXX() pattern; if it did it would be somewhere in JsonSerializer.Write.HandleObject.cs -- but it's not. The following issues track requests for conditional serialization:
.net core 3.0 system.text.json option for ignoring property at runtime like newstonsoft DefaultContractResolver #42043.
System.Text.Json option to ignore default values in serialization & deserialization #779.
To restore ShouldSerialize functionality, you can revert back to using Newtonsoft as shown in this answer to Where did IMvcBuilder AddJsonOptions go in .Net Core 3.0? by poke, and also Add Newtonsoft.Json-based JSON format support:
Install Microsoft.AspNetCore.Mvc.NewtonsoftJson.
Then call AddNewtonsoftJson() in Startup.ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddNewtonsoftJson();
}

It is possible in Net 5 to use conditional JsonIgnore. It does not give you full conditional option, but you can exclude null at least which I suppose is the most used case:
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? MyProperty { get; set; }
If one wants to allow for optional null in json, it is possible to use a custom Optional<T> struct that is similar to Nullable, like e.g. one from Roslyn. Then it's possible to have a value, null, or no field at all in the result JSON.

Related

Replace ASP.NET Core 3.1 JSON implementation system wide

Recently I have decided to go from ASP.NET Core 2.2 to ASP.NET Core 3.1 and did not anticipate all the breaking changes; nearly every part of my application broke as most parts rely on JSON.
To safeguard against future JSON related problems, would it be possible to create an interface, mimicking the current Json implementation and override the default behaviour.
Most of my code relies on these two methods:
Json.Serialize() // used in my razor
Json() // returns an IActionResult
Use case: a Razor Page : Json.Serialize Doc
<script>
var myModel = #Html.Raw(Json.Serialize(Model))
</script>
Use case: a Controller
public async Task<IActionResult> AjaxGetRoleDetails(int id)
{
return Json(await GetUserRoles(id));
}
Here are the methods that I would like, when the above methods are called respectively.
JsonConvert.SerializeObject() // override Json.Serialize
Content(JsonConvert.SerializeObject(), new MediaTypeHeaderValue("application/json")) // override Json()
How can override the system implementation, and call my own implementation
for now, and later easily revert to the system's implementation when ASP.NET settles on a JSON implementation.
The default JSON serializer for ASP.NET Core is now System.Text.Json
So you could migrate over to using it.
Or, if you want to continue using Newtonsoft.Json in ASP.NET Core 3.0 and above, you can update your Startup.ConfigureServices to call AddNewtonsoftJson.
If you require things just like before, for example, in ASP.NET Core 2.2 then you can use the default contract resolver. E.g.
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});

ASP.NET Core Web API Model binding behavior change

I have simple controller with custom model type Heading - without parameterless constructor and public setter.
I tried following code in asp.net mvc core 2.2 and 3.1.
Model class:
public class Heading
{
public string Title { get; }
public Heading(string title)
{
Title = title;
}
}
API Controller:
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpPost]
public void Post([FromBody] Heading value)
{
}
}
With .net core 2.2, binding works perfectly. But for core 3.1, it throws error
System.NotSupportedException: Deserialization of reference types
without parameterless constructor is not supported. Type
'WebApplication3.Controllers.Heading' at
System.Text.Json.ThrowHelper.ThrowNotSupportedException_DeserializeCreateObjectDelegateIsNull(Type
invalidType)
Is this change in behaviour? Can it still be achieved?
In ASP.NET Core 2.2, it works just because of Newtonsoft.Json. In ASP.NET Core Version >= 3.0 it was replaced by System.Text.Json. Here is more about System.Text.Json, the new default ASP.NET Core JSON Serializer.
If you’d like to switch back to the previous default of using Newtonsoft.Json, do the following:
First Install the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package. Then in ConfigureServices() add a call to AddNewtonsoftJson() as follows:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers()
.AddNewtonsoftJson()
...
}
For more details: ASP.NET Core 3.0 - New JSON serialization
Although you can change the default serializer and return to the previous functionality Newtonsoft.Json as mentioned by #TanvirArjel, the ideal is to use the default serializer System.Text as it has better performance. You can check it here: How to migrate from Newtonsoft.Json to System.Text.Json The error is caused due to the fact that the Heading class does not have a constructor valid without arguments which should be defined as follows.
public class Heading {
Heading(){
}
public string AttrOne {get; set;}
public string AttrTwo {get; set;}
}
From the docs:
Complex types
A complex type must have a public default constructor and public writable properties to bind. When model binding occurs, the class is instantiated using the public default constructor.
You will need to add a parameter-less constructor for binding to use that model.
The behavior may have worked for you before (2.2 and prior) if you were using NewtonsoftJson which may have allowed parameter-less constructor models. Since 3.0 .NET Core uses the newer System.Text.Json serializer by default.

System Text JsonSerializer Deserialization of TimeSpan

In researching how to deserialize a TimeSpan using Newtonsoft's JSON.net I came across code in my current project that did not use Json.net. It used System.Text.Json.JsonSerializer and appeared to not fail on the operation of deserializing the TimeSpan property, as per the unit tests I was running.
Great I thought, .Net Core 3.1 has surpassed the historical issue of deserializing a TimeSpan and all is good. So fired up a test case in the latest version of Linqpad 6 (which uses .NET Core) to verify and to my chagrin it failed.
So the question is, can the TimeSpan be serialized/deserialized using either library (and if so how)… or is my test case below flawed in some respect?
Code
public class Project { public TimeSpan AverageScanTime { get; set; } }
Linqpad C# Code
var newP = new Project() { AverageScanTime = TimeSpan.FromHours(1) };
newP.Dump("New one");
var json = System.Text.Json.JsonSerializer.Serialize(newP);
json.Dump("JSON serialized");
System.Text.Json.JsonSerializer.Deserialize<Project>(json)
.Dump("JSON Deserialize");
Deserialize Failure
JsonSerializer for TimeSpan seem will added in Future (removed from .NET 6 milestone). You can trace this issue in Future milestone or this issue.
At this time, you can implement JsonTimeSpanConverter on your own. Or you can install Macross.Json.Extensions nuget package and follow the instruction to de/serializer.
An addition to the answer from Poy Chang
Swagger (Swashbuckle) also requires a configuration
services.AddSwaggerGen(options =>
{
options.MapType(typeof(TimeSpan), () => new OpenApiSchema
{
Type = "string",
Example = new OpenApiString("00:00:00")
});
});
TimeSpanConverter is available in .NET 6.0. So TimeSpan serialization/deserialization will work without custom converters out of the box.
Issue: https://github.com/dotnet/runtime/issues/29932
Implementation: https://github.com/dotnet/runtime/pull/54186

XmlSerializerInputFormatter is obsolete - ASP.NET Core 2.1

I am using the following to accept XML serialized in my Core API App.
services.AddMvc(options =>
{
// allow xml format for input
options.InputFormatters.Add(new XmlSerializerInputFormatter());
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
After updating to ASP.NET Core 2.1 I receive the following warning:
'XmlSerializerInputFormatter.XmlSerializerInputFormatter()' is obsolete: 'This constructor is obsolete and will be removed in a future version.'
What is the new way to handle this?
According to the source code, there's a constructor that has not been marked as Obsolete:
public XmlSerializerInputFormatter(MvcOptions options)
This constructor takes an instance of MvcOptions, so you can pass through your existing options argument:
services.AddMvc(options =>
{
// allow xml format for input
options.InputFormatters.Add(new XmlSerializerInputFormatter(options));
}) ...
As of ASP.NET Core 3.0, this constructor is the only one available. Those that were marked obsolete have now been removed.
With .NET Core 2.2 or later XmlSerializerInputFormatter should be marked as deprecated.
Instead a of explicitly defining XML serializers as we did before, in the .NET Core 2.2 we can add them simply by calling AddXmlSerializerFormatters() method which will do the job now. Read here why it has been deprecated
Here is how you can do it.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(config =>
{
config.RespectBrowserAcceptHeader = true;
config.ReturnHttpNotAcceptable = true;
config.OutputFormatters.Add(new CsvOutputFormatter());
}).AddXmlSerializerFormatters().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

JSON properties now lower case on swap from ASP .Net Core 1.0.0-rc2-final to 1.0.0

I've just swapped our project from ASP .Net Core 1.0.0-rc2-final to 1.0.0. Our website and client have stopped working because of the capitalization of JSON properties. For example, this line of JavaScript now fails
for (var i = 0; i < collection.Items.length; i++){
because the controller now calls the array "items" instead of "Items". I have made no changes beyond installing the updated packages and editing the project.json file. I have not changed the C# model files which still capitalize their properties.
Why have the ASP.Net Core controllers started returning JSON with lower-cased properties? How do I go back to them honoring the case of the property names from the model?
MVC now serializes JSON with camel case names by default
Use this code to avoid camel case names by default
services.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
Source:
https://github.com/aspnet/Announcements/issues/194
In case you found this from Google and looking for a solution for Core 3.
Core 3 uses System.Text.Json, which by default does not preserve the case. As mentioned with this GitHub issue, setting the PropertyNamingPolicy to null will fix the problem.
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllers()
.AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
and if you don't want to change the global settings, for one action only it's like this:
return Json(obj, new JsonSerializerOptions { PropertyNamingPolicy = null });
You can change the behavior like this:
services
.AddMvc()
.AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());
See the announcement here: https://github.com/aspnet/Announcements/issues/194
For those who migrated to Core 3.1 and have Core MVC project can use following setup code in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddControllersWithViews().AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
...
}
This will fix it in dotnet core 3 webapi, so that it doesn't change your property names at all, and you return to your client exactly what you intended to.
In Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers().AddJsonOptions(options => options.JsonSerializerOptions.PropertyNamingPolicy = null);
services.AddHttpClient();
}
For someone who does not want to set it globally, it is possible to use ContractResolver also to return as Json result:
public IActionResult MyMethod()
{
var obj = new {myValue = 1};
return Json(obj, new JsonSerializerSettings {ContractResolver = new DefaultContractResolver()});
}
For some one who is using ASP.net WEB API ( rather than ASP.NET Core).
Add this line in your WebApiConfig.
//Comment this jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jsonFormatter.SerializerSettings.ContractResolver = new DefaultContractResolver();
Adding this as an answer here because this comes up first in google search for web api as well.
For ASP MVC Core 6 Web API , Add below code into Program.cs file will make sure JSON propreties name follow C# model properties name in right casing. No 3rd party package require
using Microsoft.AspNetCore.Mvc;
builder.Services.Configure<JsonOptions>(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
For Core 2.x versions, using this code you can avoid camel case names by default. You need to add following code inside the ConfigureServices method of Startup.cs file.
services.AddMvc()
.AddJsonOptions(o =>
{
if (o.SerializerSettings.ContractResolver != null)
{
var castedResolver = o.SerializerSettings.ContractResolver
as DefaultContractResolver;
castedResolver.NamingStrategy = null;
}
});
Recently had this issue with .Net6
The solution turned out to be that I needed to install
Microsoft.AspNetCore.Mvc.NewtonsoftJson 6.0.0.x (Note, use 7.x for .Net 7)
Found this out from Mason's post:
Microsoft.AspNetCore.Mvc.NewtonsoftJson 6.0.2 is not compatible with net5.0
Install
Microsoft.AspNetCore.Mvc.NewtonsoftJson
For .net 6 you should select this version: 6.0.13
and then go to Program.cs and configure it like this
builder.Services
.AddControllersWithViews()
.AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver());

Categories

Resources