0

My solution has a WebAPI project (.net core 3.1, Microsoft.AspNetCore.Mvc) and a (.Net Standard 2.1) class library that defines the data structures. My Controller takes a post with a single parameter that deserializes mostly correctly


public class apiRequest
{
    public RequestData TheData { get; set; }
    public Options Options { get; set; }
    public apiRequest() { }
}

The RequestData and child objects are defined i a .Net Standard 2.1 class library and added via a nuget package


public class RequestData : IRequestData
{
    public int Datum{ get; set; }
    ...
    public List<ComplexItem> ComplexItems { get; set; }
    ...
}
public class ComplexItem: ItemBase, IComplexItem
{
    public ComplexItem() : base() { }
    public ComplexItem(Pricing defaultPricing) : base(defaultPricing) { }
    [JsonConstructor]
    public ComplexItem(Pricing defaultPricing, Pricing selectedPricing) : base(defaultPricing, selectedPricing) { }
}

The problem I am running into is with the defaultPricing is always null when it gets to the controller


public class ItemBase : IItemBase
{
    public ItemBase () { }
    public ItemBase (Pricing defaultPricing)
    {
        DefaultPricing = defaultPricing;
    }
    [JsonConstructor]
    public ItemBase (Pricing defaultPricing, Pricing selectedPricing)
    {
        DefaultPricing = defaultPricing;
        SelectedPricing = selectedPricing;
    }

    #region Pricing
    [JsonProperty]
    protected Pricing DefaultPricing { get; set; }
    public Pricing SelectedPricing { get; set; }
    [JsonIgnore]
    protected Pricing CurrentPricing
    {
        get { return SelectedPricing ?? DefaultPricing; }
        set { SelectedPricing = value; }
    }
    [JsonIgnore]
    public decimal Cost { get => CurrentPricing?.Cost ?? 0; }
    [JsonIgnore]
    public decimal Price { get => CurrentPricing?.Price ?? 0; }
    #endregion
}

I've tried using [DataContract] and [DataMember] attributes, JsonObject, JsonConstructor, JsonProperty attributes and [Serializable] attribute. (Is there a current best practice on what to use?)

If I read the Json from a file and use Newtonsoft.Json.JsonConvert.DeserializeObject it deserializes correctly with the Json attributes added, but still null in the controller.

It also deserializes in the API properly if I make it public, so it doesn't seem like a problem in the Pricing class itself

user2603293
  • 71
  • 10

2 Answers2

0

After posting I found this Question about making Newtonsoft the default and using MikeBeaton's accepted solution there with Microsoft.AspNetCore.Mvc.NewtonsoftJson package worked so I'll put this as one potential answer for anyone else with this issue. Would still like to know if there is a more correct solution available.

user2603293
  • 71
  • 10
0

System.Text.Json Serializes Public Properties

As the documentation implies (emphasis mine):

By default, all (read: only) public properties are serialized. You can specify properties to exclude.

I would guess that this was the design chosen because serializing an object is allowing that object to cross barriers of scope and the public scope is the only one that can reliably be assumed.

If you think about it, it makes sense. Lets say, you define a protected property and serialize the object. Then a client picks it up and deserializates that text representation into a public property. What you have designed to be an implementation detail of/to derived types is now accessible outside the scope defined by the modifier.

Apart from simply pointing you to your own answer where Newtonsoft allows this protected property to be serialized, I would suggest you look more intently at your design and why those properties are protected in the first place. It makes sense within the context of your API implementation, but the client can't (shouldn't) be assumed to follow your same inheritance structure (or support inheritance at all). It seems like you might want to define a true DTO to act as the "shape" of your API response and find the right place to transition from your internal types using protected scope to control access and the DTO that can cross the border of the API.

Josh Gust
  • 4,102
  • 25
  • 41