0

Is it possible to deserialize an object inherited from an Abstract Class?

I have the following:

public abstract partial class Item
{
  public Item() { }
  public int ItemID { get; set; }
  public Nullable<int> ObjectStateID { get; set; }
}


public abstract partial class Appointment : Item
{
  public Appointment() { }
  public string AppointmentDescription { get; set; }
}

public partial class AppointedActivity : Appointment
{
    public Nullable<int> AppointedActivityID { get; set; }
}

public partial class AppointedDevice : Appointment
{
    public Nullable<int> AppointedDeviceID { get; set; }
}

And I have a controller that should POST Items:

public Item PostItem([FromBody]Item item)
{
  // item is always null here.
  return item;
}

The problem I'm having is that no matter what the content of my body is, the item in the controller is always null.

Example of a JSON sent to the controller:

{
  "ObjectStateID": 1,
  "AppointmentDescription": "test",
  "AppointedActivityID": 90902 // Valid Activity ID.
}

I'm using Entity Framework and I'm not sure what's wrong with that code.

Thanks!

BravoZulu
  • 1,140
  • 11
  • 24
  • 1
    The JSON needs to specify what the derived type is, otherwise there's no way for the deserialiser to figure out what to do with it. – DavidG Nov 01 '17 at 14:05
  • How would I specify it? Shouldn't adding the `AppointedActivityID` be enough to derive what type that is? Thanks for your help! – BravoZulu Nov 01 '17 at 14:07
  • 1
    What if you had two distinct derived classes that defined a property called `AppointedActivityID`? Take a look at this https://www.newtonsoft.com/json/help/html/SerializeTypeNameHandling.htm – DavidG Nov 01 '17 at 14:08
  • 1
    I believe the binder isn't able to figure it out what item to create, so I guess you could create an action filter to build and put (in the `item` argument) the correct object. – dcg Nov 01 '17 at 14:12

2 Answers2

1

You can't pass an abstract type to your controller as a parameter with the default model binder. Because model binder will have error while creating instance of this parameter.

If you would like to create your own custom model binder for this type;

public class ItemModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var values = (ValueProviderCollection) bindingContext.ValueProvider;
        var itemId = (int) values.GetValue("ItemId").ConvertTo(typeof (int));
        var objectStateId = (int?) values.GetValue("ObjectStateId").ConvertTo(typeof(int));

        //Make desicion and create the real type instance Appointment, AppointedActivity or AppointedDevice
        return (Item) new Appointment { ItemId= itemId, ObjectStateId = objectStateId };
    }
}

In Global.asax;

ModelBinders.Binders.Add(new KeyValuePair<Type, IModelBinder>(typeof(Item), new ItemModelBinder()));
Ayberk
  • 536
  • 2
  • 12
0

I finally based my solution on this and this and created a custom Contract Resolver that uses an ItemResolver:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject item = JObject.Load(reader);
        switch ((ResourceKind)item["ResourceKind"].Value<int>())
        {
            case ResourceKind.ACTIVITY:
                return item.ToObject<AppointedActivity>();
            case ResourceKind.CONSUMABLE:
                return item.ToObject<AppointedConsumable>();
            case ResourceKind.DEVICE:
                return item.ToObject<AppointedDevice>();
            default:
                throw new Exception("Invalid ResourceType");
        }
    }
BravoZulu
  • 1,140
  • 11
  • 24