Inheritance in protobuf.net, adding a lower base class still backward compatible? - c#

I have been using protobuf.net for a while and it is excellent. I can have a class which is inherited from a base class, I can serialise the derived class by using ProtoInclude statements in the base class. If my base class originally had only say two ProtoInclude statements when the object was serialised, say
[ProtoInclude(100, typeof(Vol_SurfaceObject))]
[ProtoInclude(200, typeof(CurveObject))]
internal abstract class MarketDataObject
I can still deserialise that same object in to code that has evolved to have more derivations:
[ProtoInclude(100, typeof(Vol_SurfaceObject))]
[ProtoInclude(200, typeof(CurveObject))]
[ProtoInclude(300, typeof(StaticDataObject))]
internal abstract class MarketDataObject
So far so good (in fact excellent, thanks Marc). However, now what if I want to have a base class even lower then my current base class here (in this case, MarketDataObject). Such that I would have
[ProtoInclude(100, typeof(Vol_SurfaceObject))]
[ProtoInclude(200, typeof(CurveObject))]
[ProtoInclude(300, typeof(StaticDataObject))]
internal abstract class MarketDataObject : LowerStillBaseClass
{ blah }
[ProtoInclude(10, typeof(MarketDataObject))]
internal abstract class LowerStillBaseClass
{ blah }
Whilst the code will of course work, will I be still be able to deserialise the initial objects that were serialised when the object had only 2 ProtoInclude statements to this new form of the MarketDataObject class?

This will not work purely with static protbuf-net attributes. Simplifying somewhat, imagine you start with the following :
namespace V1
{
[ProtoContract]
internal class MarketDataObject
{
[ProtoMember(1)]
public string Id { get; set; }
}
}
And refactor it to be the following:
namespace V2
{
[ProtoInclude(10, typeof(MarketDataObject))]
[ProtoContract]
internal abstract class LowerStillBaseClass
{
[ProtoMember(1)]
public string LowerStillBaseClassProperty { get; set; }
}
[ProtoContract]
internal class MarketDataObject : LowerStillBaseClass
{
[ProtoMember(1)]
public string Id { get; set; }
}
}
Next, try to deserialize a created from the V1 class into a V2 class. You will fail with the following exception:
ProtoBuf.ProtoException: No parameterless constructor found for LowerStillBaseClass
The reason this does not work is that type hierarchies are serialized base-first rather than derived-first. To see this, dump the protobuf-net contracts for each type by calling Console.WriteLine(RuntimeTypeModel.Default.GetSchema(type)); For V1.MarketDataObject we get:
message MarketDataObject {
optional string Id = 1;
}
And for V2.MarketDataObject:
message LowerStillBaseClass {
optional string LowerStillBaseClassProperty = 1;
// the following represent sub-types; at most 1 should have a value
optional MarketDataObject MarketDataObject = 10;
}
message MarketDataObject {
optional string Id = 1;
}
MarketDataObject is getting encoded into a message with its base type fields first, at the top level, then derived type fields are recursively encapsulated inside a nested optional message with a field id that represents its subtype. So when a V1 message is deserialized to a V2 object, no subtype field is encountered, the correct derived type is not inferred, and derived type values are lost.
One workaround is to avoid using [ProtoInclude(10, typeof(MarketDataObject))] and instead populate the base class members in the derived type's contract programmatically using the RuntimeTypeModel API:
namespace V3
{
[ProtoContract]
internal abstract class LowerStillBaseClass
{
[ProtoMember(1)]
public string LowerStillBaseClassProperty { get; set; }
}
[ProtoContract]
internal class MarketDataObject : LowerStillBaseClass
{
static MarketDataObject()
{
AddBaseTypeProtoMembers(RuntimeTypeModel.Default);
}
const int BaseTypeIncrement = 11000;
public static void AddBaseTypeProtoMembers(RuntimeTypeModel runtimeTypeModel)
{
var myType = runtimeTypeModel[typeof(MarketDataObject)];
var baseType = runtimeTypeModel[typeof(MarketDataObject).BaseType];
if (!baseType.GetSubtypes().Any(s => s.DerivedType == myType))
{
foreach (var field in baseType.GetFields())
{
myType.Add(field.FieldNumber + BaseTypeIncrement, field.Name);
}
}
}
[ProtoMember(1)]
public string Id { get; set; }
}
}
(Here I am populating the contract inside the static constructor for MarketDataObject. You might want to do it elsewhere.) The schema for V3. looks like:
message MarketDataObject {
optional string Id = 1;
optional string LowerStillBaseClassProperty = 11001;
}
This schema is compatible with the V1 schema, and so A V1 message can be deserialized into a V3 class without data loss. Sample fiddle.
Of course, if you are moving a member from MarketDataObject to LowerStillBaseClass you will need to ensure that the field id stays the same.
The disadvantage of this workaround is that you lose the ability to deserialize an object of type LowerStillBaseClass and have protobuf-net automatically infer the correct derived type.

Related

Exception when serializing polymorphic c# class with method hiding to json, ASP.NET core API

To get deep into the problem, here is a console app :
class Program
{
static void Main()
{
Console.WriteLine(JsonSerializer.Serialize(
new PolyMorphicClass { Data = new SomeData { N = 2} }));
}
class BaseClass
{
public virtual object Data { get; set; }
}
class PolyMorphicClass : BaseClass
{
public new SomeData Data { get; set; }
}
class SomeData
{
public int N { get; set; }
}
}
This code throw an invalid operation exception with this message :
The JSON property name for 'ConsoleApp_for_test.Program+PolyMorphicClass.Data' collides with another property.
I found that if I initializing BaseClass instead, like below, it works
static void Main()
{
Console.WriteLine(JsonSerializer.Serialize(
new BaseClass { Data = new SomeData { N = 2} }));
}
My actual problem is: in a WebAPI where PolymorphicClass is the response type of a controller action that is being serialized to json, and this same exception happens.
extra note: In the API I use this polymorphic behavior to make the response consistent across endpoints i.e. similar data type.
My questions are : is it ok to use the BaseClass instead of the polymorphicClass like I said above in the context of initializing API response? Is there other solutions to serialize this? Can someone explain why the exception is happening?
You can't change the return type with the new keyword, all it does is hide it and requires the use of the same signature.
You could fix this in a couple of ways.
Using Generics
Replacing object with a generic type would allow for you to define PolyMorphicClass with a specific type for Data, which I believe is similar to what you're trying to do here.
class BaseClass<T>
{
public virtual T Data { get; set; }
}
class PolyMorphicClass : BaseClass<SomeData>
{
}
Provide implementations for the property
Properties are essentially 2 methods (a getter and a setter) and you use some default ones with { get; set; }. These defaults get and set, respectively, a private member underneath the hood.
virtual properties are basically saying "You should override my getter and setter". Just specify an underlying member with the type SomeData to get and set. Here's a basic example.
class BaseClass
{
public virtual object Data { get; set; }
}
class PolyMorphicClass : BaseClass
{
private SomeData data { get; set; }
public override object Data
{
get
{
return data;
}
set
{
data = (SomeData) value;
}
}
}
Note that if you deserialize some JSON that can't be casted to SomeData you'll run into a runtime exception of System.InvalidCastException, so you may want to add some additional type checking in your setter.

How do I get only a shadowed property, not the base property, to get JSON serialized?

In my ASP.NET MVC web application, I am using the built in Controller.Json() method to serialize an object and send it back to the client in response to an AJAX call. The class of the object being serialized inherits from another class with some shared property names. This is intentional, as I need the property names to match for some reflection that's happening. I am "shadowing" those properties in the derived class so that they can be a different type from their same-name counterpart in the base class. Here's a simplified example:
public class BaseModel
{
public string Title { get; set; }
public decimal CleanUpHours { get; set; }
public decimal InstallHours { get; set; }
}
public class DerivedModel : BaseModel
{
public new BucketHoursWithCalculations CleanUpHours { get; set; }
public new BucketHoursWithCalculations InstallHours { get; set; }
}
When I serialize an instance of DerivedModel, my JSON object on the client contains only the decimal versions of CleanUpHours and InstallHours, not my custom class BucketHoursWithCalculations.
Inspecting the object in Visual Studio before it gets serialized shows both the base and derived versions of those properties, as shown here (please excuse all the extra properties — my sample classes above are more simplified than what I'm actually using, but the principle is the same):
Here's what that object looks like on the client once it's serialized into JSON:
As you can see, the derived/shadowed properties were not serialized, and the base properties were, but only in the cases where there was a name conflict (for example, the Title property in the base model serialized just fine).
How can I serialize only the shadowed properties where there's a name conflict? I don't believe changing the access modifiers (i.e. from public to protected or something) on the base properties will work in my case, because the BaseModel is used by Entity Framework, and must have public properties. Any help would be appreciated.
One idea is to define type parameter on the base model that is used for the hours properties. Then, define derived models for decimal and BucketHoursWithCalculations. I would be interested to see how BucketHoursWithCalculations serializes to JSON, but in any case the CleanUpHours and InstallHours properties should be serialized.
// use a type parameter on the base model that must be specified
// in derived models.
public class BaseModel<THours>
{
public string Title { get; set; }
public THours CleanUpHours { get; set; }
public THours InstallHours { get; set; }
}
// hours are specified as decimals
public class DecimalModel : BaseModel<decimal>
{
}
// hours are specified as BucketHoursWithCalculations
public class BucketHoursWithCalculationsModel : BaseModel<BucketHoursWithCalculations>
{
}
// usage
DecimalModel d = new DecimalModel();
d.CleanUpHours = 1.0M; // CleanUpHours is a decimal here
BucketHoursWithCalculationsModel b = new BucketHoursWithCalculationsModel();
b.CleanUpHours = new BucketHoursWithCalculations();
b.CleanUpHours.SomeProperty = 1.0M;

ServiceStack: Property in request DTO becomes null if type is abstract

I have a ServiceStack 3-based client-server architecture. I'm trying to create a service whose request DTO contains a property with an abstract type, with two different concrete classes implementing it. The abstract type could be either an abstract class or an interface; however, in either case, the server receives a null object in the property.
There's three assemblies and corresponding namespaces: TestClient, Server, and CommonLib referenced by both client and server.
That is, spread across the three assemblies:
namespace CommonLib.Services
{
public class GetThing : IReturn<GetThingResponse> // request DTO
{
public IThisOrThat Context { get; set; }
}
public class GetThingResponse
{
public Dictionary<int, string> Result { get; private set; }
public GetThingResponse(Dictionary<int, string> result) // response DTO
{
Result = result;
}
}
}
namespace CommonLib
{
public interface IThisOrThat { }
public class This : IThisOrThat { } // and so forth
}
namespace Server.Services
{
public class GetThing Service : IService
{
public object Get(GetThing request)
{
var foo = request.Context; // this is null
}
}
}
namespace TestClient
{
class Program
{
public const string WSURL = "http://localhost:61435/";
static void Main(string[] args)
{
using (var client = new JsonServiceClient(WSURL))
{
var result = client.Get(new GetThing
{
Context = new CommonLib.This("context info")
});
}
}
If I change the Context property in GetThing to be of type This instead of IThisOrThat, this works. Leaving it as the interface, or changing IThisOrThat to be an abstract class, results in the data being transmitted as null.
I'm assuming this is a serialization problem. I've tried changing the interface to an abstract class and decorating that with appropriate KnownType attributes, but ServiceStack's serializer doesn't appear to benefit from this. Is there any trick to get this done?
You would need to enable JsConfig.IncludeTypeInfo = true; on the client side, so the serializer includes the type information with the request. This will add an extra property (__type) with the type definition so the service knows what to type it as.
It fails currently because requests by default don't provide type information to deserialize the object into the class that implements the interface. This was an issue that was previously raised.
The problem is the when the JSON client makes the request, it will serialize up the a class that implements IThisOrThat such as your This class. But when it gets to the other end ServiceStack.Text doesn't know what to deserialize the object into. The type information is lost so it doesn't know what kind of IThisOrThat it is. So without the additional __type information property in the request this is happening:
Scenario:
interface ISomething
{
string Name;
}
class MySomething : ISomething
{
public string Name { get; set; }
public int Age { get; set; }
}
class MySomethingElse : ISomething
{
public string Name { get; set; }
public int Size { get; set; }
}
Then you make the call from your JsonServiceClient using a typed object
client.Get(new MySomething { Name: "Duck", Age: 20 });
The JSON that is sent would be { "Name":"Duck", "Age":20 } what type does the deserialiser choose now? It could be an MySomething or a MySomethingElse, or even another ISomething that it just doesn't know about yet. So because it can't decide the result is simply null.
Generally interfaces and DTOs don't mix, see here.
I had a similar problem, and realized i didn't have { get; set; } applied to the response DTO, so the result of my object was always null...
Thought this information could also help anyone searching for this ...

Object serialization and derived classes; controlling xml output

I have 2 derived classes that will be serialized into xml.
While the code works fine (XmlSerializer, nothing strange), the serialization of DataScenario causes its MyData property items to produce Xmlelement names from the base class name:
<DataScenario>
<MyData>
<ScenarioData/>
<ScenarioData/>
<ScenarioData/>
</MyData>
<DataScenario>
Instead, i'm trying to have these items produce XmlElement names from their derived classes
<DataScenario>
<MyData>
<type1/>
<type1/>
<type2/>
</MyData>
<DataScenario>
Is this even possible? Keep in mind I need to deserialize as well; I'm unsure whether the Deserialize process will understand that derived objects need to be created.
Sample code i'm using is as follows.
[Serializable]
[XmlInclude(typeof(Type1))]
[XmlInclude(typeof(Type2))]
public class Scenario
{
[XmlElement("location")]
public string Location { get; set; }
[XmlElement("value")]
public string Value { get; set; }
public Scenario()
{
}
}
[Serializable]
[XmlType("type1")]
public class Type1 : Scenario
{
public FillPointData() : base() { }
}
[Serializable]
[XmlType("type2")]
public class Type2 : Scenario
{
public TestData() : base() { }
}
//Hosting class of all scenarios
public DataScenario()
{
public List<Scenario> MyData{ get; set; }
}
You can define what kind of Elements are in the Collection with the XmlArrayItem attribute.
If the Type is known (defined as you did with the XmlInclude attribute) it will create Tags "Type1", "Type2". If the Types are not known, it will still create a Tag called ScenarioData with an Attribute xsi:type="Type1" which is used to map the type while deserialization.
[XmlArrayItem(typeof(Type1))]
[XmlArrayItem(typeof(Type2))]
Public List<Scenario> Children
{
// getter & setter
}

Honouring of AttributeUsage on derived attribute types

Given the following, I would not expect the compiler to allow multiple attributes that are derived from the base attribute, given that is set to AllowMultiple=false. In fact it compiles without a problem - what am I missing here?
using System;
[AttributeUsage(AttributeTargets.Property,AllowMultiple=false,Inherited=true)]
abstract class BaseAttribute : Attribute { }
sealed class DerivedAttributeA : BaseAttribute { }
sealed class DerivedAttributeB : BaseAttribute { }
class Sample1
{
[DerivedAttributeA()]
[DerivedAttributeB()]
public string PropertyA{ get; set; } // allowed, concrete classes differ
[DerivedAttributeA()]
[DerivedAttributeA()]
public string PropertyB { get; set; } // not allowed, concrete classes the same, honours AllowMultiple=false on BaseAttribute
}
The problem is simply that the AllowMultiple check only compares attributes of the same actual type (i.e. the concrete type instantiated) - and is perhaps best used with sealed attributes for this reason.
It will, for example, enforce the following (as an illegal duplicate), inheriting this from BaseAttribute:
[DerivedAttributeB()]
[DerivedAttributeB()]
public string Name { get; set; }
In short, I don't think you can do what you want here... (enforce no more than one instance including subclasses of BaseAttribute per property).
A similar example of this problem would be:
[Description("abc")]
[I18NDescriptionAttribute("abc")]
public string Name { get; set; }
class I18NDescriptionAttribute : DescriptionAttribute {
public I18NDescriptionAttribute(string resxKey) : base(resxKey) { }
}
The intent above is to provide a [Description] from resx at runtime (fully supported by ComponentModel etc) - but it can't stop you also adding a [Description].

Categories

Resources