WebAPI deserialization of a protected property is null - c#

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

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.

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.

Related

Customize serialization to JSON to leave some fields

I have a .NET Core API project backed by MongoDB.
I have a sample model class:
public class MyModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string Id { get; set; }
[BsonElement("aField")]
public string AField { get; set; }
[BsonElement("hiddenField")]
public string HiddenField { get; set; }
}
What I want to achieve is to set the HiddenField to null every time when it is returned as the payload in my controller. I am thinking of a custom JSON serializer but don't know how to do it. Can anyone help?
NB: I don't want to manually set the HiddenField to null in every single controller or action.
Apply JsonIgnore attribute to HiddenField property.
public class MyModel
{
// ... other members
[JsonIgnore]
[BsonElement("hiddenField")]
public string HiddenField { get; set; }
}
Since under the hood ASP.Net Core uses Newtonsoft.Json (aka Json.NET) library to de/serialize JSON payloads, you can control serialization and deserialization with the attributes from that library.
Note that JsonIgnore attribute works in both directions: if a client sends hiddenField to the server, the controller won't populate it in the model.
It also worth noting that starting with ASP.NET Core 3.0, Json.NET won't necessarily be the default mechanism of JSON serialization (see this announcement): in the 3.x versions of ASP.NET Core, one must ensure that integration with Json.NET is plugged in, for current solution to work.
Regarding the architectural discussion in the OP comments, it all depends. Of course, mixing such attributes as JsonIgnore and BsonElement in one class means coupling of two separate concerns: service API and data persistence. However, in small and simple applications, proper separation of concerns might be an overkill, and the most straightforward approach might be a better fit.
I find it easier to create a ViewModel (I tend to use ViewModel as a definition of any model I expose, either by sending it to a server side presentation layer or any external resource REST/etc via JSON/XML/etc).
In this instance, I'd create:
public class MyModelVM
{
public string Id { get; set; }
public string AField { get; set; }
public string HiddenField { get; }
}
HiddenField has no set, so it will always be null. Then set your serialization options and return your JSON.
var myModelVM = new MyModelVM(); // however you want to create/map the values
var json = JsonConvert.SerializeObject(myModelVM,
Newtonsoft.Json.Formatting.None,
new JsonSerializerSettings {
NullValueHandling = NullValueHandling.Include
});
If you didn't map values to MyModelVM, then the resulting JSON would look like:
{
"Id" : null,
"AField" : null,
"HiddenField" : null,
}

C# Swashbuckle: Obsolete model property shows without change in swagger

I've marked a property as obsolete in my (input)model
public class MyModel
{
[Obsolete("Use 'OtherProperty'")]
public string SomeProperty {get;set;}
public List<string> OtherProperty {get;set;}
}
However, swagger shows no distinction between the two properties, neither does it show the message.
Is there any way I can get swagger to honor the Obsolete attribute? Or will I need to put this in the xml-comments above the property myself?
Unfortunately there is no support for obsolete properties on Swashbuckle yet...
We are limited by the OpenAPI-Specification, and Swashbuckle still using 2.0
The closest thing is deprecated but that is available only for the methods not for properties:
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
One option will be to hack something using an IDocumentFilter to completely hide those properties tagged with Obsolete but that will be a bumpy road.
Another option is to create two methods and two models, that way you can tag the method and that will transition to the method within, everything will be deprecated (I think this is a bit messy) but I have seen this pattern used in many web-api
I think your best/easiest solution is what you suggested add some xml comments noting that the property should not be used.
[Obsolete]
public string Property {get; set;}
services.AddSwaggerGen(x =>
// your other settings...
x.IgnoreObsoleteProperties();
)
This works for me
It works for me by simply decorating property with [Obsolete] attribute (from System namespace) and setting the Swagger flag IgnoreObsoleteProperties to true. I added also property SomePropertySpecified, which is automatically set to true by serializer in case SomeProperty exists in request (null value does not mean that property did not exist). I have a custom logic to return appropriate error message if SomePropertySpecified is true.
public class Item
{
[Obsolete]
public string SomeProperty { get; set; }
[JsonIgnore]
public bool SomePropertySpecified { get; set; }
public List<string> OtherProperty { get; set; }
}
Class SwaggerConfig:
public class SwaggerConfig
{
public static void Register()
{
GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "Demo");
c.IgnoreObsoleteProperties();
})
.EnableSwaggerUi(c =>
{
c.DocExpansion(DocExpansion.Full);
});
}
}
Swagger UI:

Managing multiple versions of object in JSON

I have a class in C#, that has a number of variables. Let's call it "QuestionItem".
I have a list of this object, which the user modifies, and then sends it via JSON serialization (with Newtonsoft JSON library) to the server.
To do so, I deserialize the objects that are already in the server, as a List<QuestionItem>, then add this new modified object to the list, and then serialize it back to the server.
In order to display this list of QuestionItems to the user, I deserialize the JSON as my object, and display it somewhere.
Now, the problem is - that I want to change this QuestionItem and add some variables to it.
But I can't send this NewQuestionItem to the server, because the items in the server are of type OldQuestionItem.
How do I merge these two types, or convert the old type to the new one, while the users with the old version will still be able to use the app?
You are using an Object Oriented Language, so you might aswell use inheritance if possible.
Assuming your old QuestionItem to be:
[JsonObject(MemberSerialization.OptOut)]
public class QuestionItem
{
[JsonConstructor]
public QuestionItem(int Id, int Variant)
{
this.Id = Id;
this.Variant = Variant;
}
public int Id { get; }
public int Variant { get; }
public string Name { get; set; }
}
you can extend it by creating a child class:
[JsonObject(MemberSerialization.OptOut)]
public class NewQuestionItem : QuestionItem
{
private DateTime _firstAccess;
[JsonConstructor]
public NewQuestionItem(int Id, int Variant, DateTime FirstAccess) : base(Id, Variant)
{
this.FirstAccess = FirstAccess;
}
public DateTime FirstAccess { get; }
}
Note that using anything different than the default constructor for a class requires you to use the [JsonConstructor] Attribute on this constructor and every argument of said constructor must be named exactly like the corresponding JSON properties. Otherwise you will get an exception, because there is no default constructor available.
Your WebAPI will now send serialized NewQuestionItems, which can be deserialized to QuestionItems. In fact: By default, JSON.NET as with most Json libraries, will deserialize it to any object if they have at least one property in common. Just make sure that any member of the object you want to serialize/desreialize can actually be serialized.
You can test the example above with the following three lines of code:
var newQuestionItem = new NewQuestionItem(1337, 42, DateTime.Now) {Name = "Hello World!"};
var jsonString = JsonConvert.SerializeObject(newQuestionItem);
var oldQuestionItem = JsonConvert.DeserializeObject<QuestionItem>(jsonString);
and simply looking at the property values of the oldQuestionItem in the debugger.
So, this is possible as long as your NewQuestionItem only adds properties to an object and does neither remove nor modify them.
If that is the case, then your objects are different and thus, requiring completely different objects with a different URI in your API, as long as you still need to maintain the old instance on the existing URI.
Which brings us to the general architecture:
The most clean and streamline approach to what you are trying to achieve is to properly version your API.
For the purpose of this link I am assuming an Asp.NET WebApi, since you are handling the JSON in C#/.NET. This allows different controller methods to be called upon different versions and thus, making structural changes the resources your API is providing depending on the time of the implementation. Other API will provide equal or at least similar features or they can be implemented manually.
Depending on the amount and size of the actual objects and potential complexity of the request- and resultsets it might also be worth looking into wrapping requests or responses with additional information. So instead of asking for an object of type T, you ask for an Object of type QueryResult<T> with it being defined along the lines of:
[JsonObject(MemberSerialization.OptOut)]
public class QueryResult<T>
{
[JsonConstructor]
public QueryResult(T Result, ResultState State,
Dictionary<string, string> AdditionalInformation)
{
this.Result = result;
this.State = state;
this.AdditionalInformation = AdditionalInformation;
}
public T Result { get; }
public ResultState State { get; }
public Dictionary<string, string> AdditionalInformation { get; }
}
public enum ResultState : byte
{
0 = Success,
1 = Obsolete,
2 = AuthenticationError,
4 = DatabaseError,
8 = ....
}
which will allow you to ship additional information, such as api version number, api version release, links to different API endpoints, error information without changing the object type, etc.
The alternative to using a wrapper with a custom header is to fully implement the HATEOAS constraint, which is also widely used. Both can, together with proper versioning, save you most of the trouble with API changes.
How about you wrapping your OldQuestionItem as a property of QuestionItem? For example:
public class NewQuestionItem
{
public OldQuestionItem OldItem { get; set; }
public string Property1 {get; set; }
public string Property2 {get; set; }
...
}
This way you can maintain the previous version of the item, yet define new information to be returned.
Koda
You can use something like
public class OldQuestionItem
{
public DateTime UploadTimeStamp {get; set;} //if less then DateTime.Now then it QuestionItem
public string Property1 {get; set; }
public string Property2 {get; set; }
...
public OldQuestionItem(NewQuestionItem newItem)
{
//logic to convert new in old
}
}
public class NewQuestionItem : OldQuestionItem
{
}
and use UploadTimeStamp as marker to understand, what Question is it.

Serializer ignores properties that use another serialized property

I am working on an endpoint in asp.net that serializes and returns some data, using the default serializer.
The consuming applications are transitioning between changing names for properties (in other words, some existing applications are using names like ...Vat; while newer ones are using ...Tax. I therefore need to keep both names in the response for the moment, until these changes are complete.
The return type is IList.
public class Product
{
...
public decimal PriceIncVat { get; set; }
public decimal PriceIncTax { get { return PriceIncVat; } }
public int TaxCode { get; set; }
...
}
However, when I examine the response in fiddler, only the PriceIncVat property exists in the json list of products.
I can't think of any reason why the above wouldn't work. I added TaxCode at the same time as PriceIncTax, and it is returned, so I know the code of the endpoint is up to date.
And on the client side of a newer client project we have:
public class ProductDto
{
...
public decimal PriceIncTax { get; set; }
public string TaxCode { get; set; }
...
}
Very confused here.
The serializer assumes you will need to deserialize the data some time. Hence by default only properties with a getter and a setter are considered.
When using the DataContractJsonSerializer, it's possible to turn on serialization of read-only properties using the SerializeReadOnlyTypes property (despite its rather misleading name).
Side note: Check-out the Json.NET serializer, which gives more options and better control over the (de)serialization process.

Serializing complex objects for WCF

I'm trying to pass a complex object via Windows Communication Foundation, but I get Read errors. I'm able to binaryFormat the object to a file and reload and deserialize it. All the components/ referenced component Classes are marked with the Serializable attribute. As a work round I have serialized the object to a memory stream, passed the memory stream over WCF and then deSerialized the memory stream at the other end. Although I could live with this solution it doesn't seem very neat. I can't seem to work out what the criteria are for being able to read from the proxy. Relatively simple objects, even ones that include a reference to another class can be be passed and read without any attribute at all. Any advice welcomed.
Edit: Unrecognised error 109 (0x6d) System.IO.IOException the Read Operation Failed.
Edited As Requested here's the class and the base class. Its pretty complicated that's why I didn't include code at the start, but it binary serializes fine.
[Serializable]
public class View : Descrip
{
//MsgSentCoreDel msgHandler;
public Charac playerCharac { get; internal set;}
KeyList<UnitV> unitVs;
public override IReadList<Unit> units { get { return unitVs; } }
public View(Scen scen, Charac playerCharacI /* , MsgSentCoreDel msgHandlerI */)
{
playerCharac = playerCharacI;
//msgHandler = msgHandlerI;
DateTime dateTimeI = scen.dateTime;
polities = new PolityList(this, scen.polities);
characs = new CharacList(this, scen.characs);
unitVs = new KeyList<UnitV>();
scen.unitCs.ForEach(i => unitVs.Add(new UnitV(this, i)));
if (scen.map is MapFlat)
map = new MapFlat(this, scen.map as MapFlat);
else
throw new Exception("Unknown map type in View constructor");
map.Copy(scen.map);
}
public void SendMsg(MsgCore msg)
{
msg.dateT = dateTime;
//msgHandler(msg);
}
}
And here's the base class:
[Serializable]
public abstract class Descrip
{
public DateTime dateTime { get; set; }
public MapStrat map { get; set; }
public CharacList characs { get; protected set; }
public PolityList polities { get; protected set; }
public abstract IReadList<Unit> units { get; }
public GridElList<Hex> hexs { get { return map.hexs; } }
public GridElList<HexSide> sides { get { return map.sides; } }
public Polity noPolity { get { return polities.none; } }
public double hexScale {get { return map.HexScale;}}
protected Descrip ()
{
}
public MapArea newMapArea()
{
return new MapArea(this, true);
}
}
I suggest that you take a look at the MSDN documentation for DataContracts in WCF since that provides some very helpful guidance.
Update
Based on the provided code and exception information, there are two areas of suspicion:
1) Collections and Dictionaries, especially those that are generics-based, always give the WCF client a hard time since it will not differentiate between two of these types of objects with what it considers to be the same signature. This will usually result in a deserialization error on the client, though, so this may not be your problem.
If it is your problem, I have outlined some of the steps to take on the client in my answer to this question.
2) You could have, somewhere in your hierarchy, an class that is not serializable.
If your WCF service is hosted in IIS, then the most invaluable tool that I have found for tracking down this kind of issue is the built-in WCF logger. To enable this logging, add the following to your web.config file in the main configuration section:
After you have generated the error, double-click on the svclog file and the Microsoft Service Trace Viewer will be launched. The items in red on the left-hand side are where exceptions occur and after selecting one, you can drill into its detail on the right hand side and it usually tells you exactly which item it had a problem with. Once we found this tool, tracking down these issues went from hours to minutes.
You should use DataContract and DataMember attributes to be explicit about which fields WCF should serialise, else also implement ISerializable and write (de-)serialisation yourself.

Categories

Resources