Mimicing Inheritance in Protobuf-net v2 - c#

So I have a .NET server and an IOS client and I'm trying to use protocol buffers to communicate back and forth. On the server I have one object that contains a list of FormField objects where FormField is actually going to be a subtype of FormField like say ButtonFormField.
After doing some research, I implemented my .proto files similar to this: Protobuf-net .proto file generation for inheritance
Which works fine for serializing from the server to the client.
Unfortunately when I send items back to the server, I get errors because it is trying to cast FormField as ButtonFormField (which obviously doesn't work) in CompiledSerializer.cs line 49.
Does this not work in protobuf-net v2 or am I doing something wrong? I am really hoping to get this working.
EDIT*
I tried this using protobuf-net v1 and it does work, so I'm assuming this behavior changes in v2. Is there an alternate way to do this in v2 or am I stuck on v1 for now?
Edit 2:
Sorry for the delay Marc. Here is a cut down .proto and the C# classes. I am using protoc to generate the IOS classes, so I am assuming generating C++ or Java with protoc as the client would generate the same effect also.
Working Sample Proto File
message PersonSelectionFormField {
}
message TextFormField {
}
message FormFieldDto {
//Properties and optional properties truncated for brevity
required int32 Id = 1;
required int32 FieldTemplateId = 6;
required int32 RowId = 9;
optional PersonSelectionFormField PersonSelectionFormField = 55;
optional TextFormField TextFormField = 59;
}
Classes
namespace Sample.Fields
{
//I get a list of FormFields back from the client
[ProtoContract]
[ProtoInclude(55, typeof(PersonSelectionFormField))]
[ProtoInclude(59, typeof(TextFormField))]
public class FormField:IFormField
{
//Cleaned up for brevity...There are virtual methods and fields
//getting overriden but they aren't relevant to the example
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(6)]
[Required]
public int FieldTemplateId { get; set; }
[ProtoMember(9)]
[Required]
public int RowId { get; set; }
}
public class TextFormField:FormField
{
//Some overriden field / methods.
//None of the serialized fields are overriden
}
public class PersonSelectionFormField:FormField
{
//Some overriden field / methods.
//None of the serialized fields are overriden
}
}

Related

When returning a list of an interface from an API controller, how to include the subclasses' properties in the JSON response?

Situation
I'm writing an application that can display different types of charts, with a vanilla JS front-end and an ASP.NET 6 API back-end. There are different types of charts in the application, like LineChart and PieChart. These different chart types all implement the interface IChart. The IChart interface has properties for information like the ID and name of the chart, whereas the LineChart, for example, has properties for the name of the X- and Y-axis.
Here are the model classes as minimum example:
IChart.cs:
public interface IChart
{
public int Id { set; get; }
public string Title { set; get; }
public string Description { set; get; }
}
LineChart.cs:
public class LineChart : IChart
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string xAxisName { get; set; }
public string yAxisName { get; set; }
}
My Problem
When I'm returning a single chart using an ASP.NET API-controller, it works just fine; if the object I return is a LineChart, I get the property xAxisName in the JSON response even if the API method return type is IChart. But when I return multiple Charts in a List<IChart>, I only get the properties provided by IChart in my JSON, so no xAxisName. How can I make a controller return all the items in the List<IChart while keeping the properties of the derived classes in the JSON result? Is there some JSON middleware setting I need to set?
As an illustration, here are a few sample API calls.
The following code returns a LineChart with the X axis name set in the JSON reponse body:
// POST: api/<ChartController>
[HttpPost("{xAxisName}")]
public IChart MakeOneChart(string xAxisName)
{
IChart chart = new LineChart() {xAxisName = xAxisName};
return chart;
}
But in the following code, the response body is not filled with values for the X axis
// GET: api/<ChartController>
[HttpPost]
public IEnumerable<IChart> MakeManyCharts()
{
IEnumerable<IChart> charts = new List<IChart>()
{
new LineChart() {xAxisName = "Duration (in minutes)"},
new LineChart() {xAxisName = "Duration (in years)"}
};
return charts;
}
People here had a similar problem. The returned object has an interface as a property and the derived classes didn't serialize properly but I bet any property defined in that interface would show.
Now I don't know much about the inner workings of Json conversion but from what I've observed, here's what I'm guessing is happening:
The standard Json serializer provided with .Net doesn't recognize the object's runtime type so it doesn't know anything about the object beneath the surface. Only exception is the outermost object you're returning. For example, when your return type is IChart, it can recognize when you return LineChart or PieChart since it's just checking the outermost object. However, when you return IEnumerable<IChart> it can't recognize the derived type of IChart objects since they're wrapped by IEnumerable. I bet if you returned List<IChart> it could recognize properties related to List<T>
There are some possible solutions:
An answer in the linked question suggests using Json.NET package from Newtonsoft
A different answer from the question suggests writing custom serializers
Another option I would say would be to try writing a custom formatter. Check the type of object and cast it like this:
if (context.Object is IEnumerable<LineChart> lineCharts)
{
// serialize the lineCharts
}
else if (context.Object is IEnumerable<PieChart> pieCharts)
{
// serialize the pieCharts
}
// return the json string
though this type of solution probably would not work if you have base interfaces as properties like in the question and you wanted the derived objects of those to be recognized in the serialization (or rather you would have to write nested if statements and check every property of every chart object in the IEnumerable and it would go on and on if you have a very complex object), but should be enough for your situation

Exception handling in GRPC - return common object

I googled a lot C# articles how to proceed with that with interceptors. I can divide them on 2 types:
Rethrow RPCException
return default
My problem, i want to return back some common API response object.
public class GrpcResponseBase
{
public int StatusCode { get; set; }
public string ErrorMessage { get; set; }
}
public class GrpcResponse<TData> : GrpcResponseBase
{
public TData Data { get; set; }
...
}
So all the objects i return to client need to be based on that. And the object returned from exception handler too.
But that's the problem. The response objects are autogenerated by protobuf compiler from proto files. Proto doesn't support inheritance, and i don't want to copy-paste those 2-3 fields each time for each "message". And i think it doesn't support generics too.
What can i do ? Maybe don't use interceptors, but use something else ? Please suggest

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