Reputation: 18684
What’s the right way to do this. I have an api that handles vehicles for example. I want a POST and GET endpoint that saves and gets a vehicle. But a vehicle can be a car, bus or truck. That have different properties. But in my database, they are Vehicle types, and depending on the VehcileTypeId, join to either a Car, Bus or Truck table.
In code, a Bus caked inherits from Vehicle class with common fields. But the Bus class has extra properties.
Is there a way to have a SaveVehicle endpoint that accepts Vehicle type? But has the fields submitted from the UI? Or do I need a SaveBus, SaveCar, SaveTruck endpoint?
I’m not sure what my request object might be.
public async Task Save(GenricVehicleRequest request)
Upvotes: 0
Views: 379
Reputation: 2137
From REST perspective you better of with separate SaveBus, SaveCar and SaveTruck endpoints, cause otherwise you lose self-description and resource identification (though, it is discussable) But if you really need this, you can do this, and moreover it is supported by Swagger (OpenAPI), there this feature is called polymorphism (surprise-surprise) - https://swagger.io/docs/specification/data-models/inheritance-and-polymorphism/
How to achieve in .NET:
In your base class you need a discrimantor property by which later on you will decide what real type to use in your custom json converter:
public class Vahicle
{
public string Discriminator {get;set;}
...
}
Then you create your own custom JSON Converter:
public class VahicleConverter : JsonConverter
{
private string _discriminator { get; set; } = "Discriminator";
public VahicleConverter ()
{
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var jDerivedObject = JObject.Load(reader);
var discriminator = jDerivedObject.Value<string>(_discriminator);
if (string.IsNullOrWhiteSpace(discriminator))
throw new Exception("Invalid discriminator value");
var derivedType = objectType;
switch(discriminator)
{
case "Bus":
derivedType = typeof(Bus);
case "Truck ":
derivedType = typeof(Truck);
default:
throw new Exception("Unknown type");
}
var derivedObject = Activator.CreateInstance(derivedType);
serializer.Populate(jDerivedObject.CreateReader(), derivedObject);
return derivedObject;
}
public override bool CanConvert(Type objectType) => true;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
//Not required
public override bool CanWrite => false;
}
Then you need to instruct .NET JSON serializer to use your custom serializer, by simple adding [JsonConverter(typeof(VahicleConverter))]
attribute to your class:
[JsonConverter(typeof(VahicleConverter))]
public class Vahicle
{
public string Discriminator {get;set;}
...
}
Now you can have you API action like:
[HttpPost]
public Task<Result> AddVahicle([FromBody]Vahicle model)
And request your action with JSON containing your discriminator:
{
Dicriminator:"Bus",
BusSpecificProp:"1",
...
}
Sorry if above code have some typos, i haven't run this exact code, but i've used similar in the past, and yeah, additional kudos goes to this answer - https://stackoverflow.com/a/51815457/7064030
Upvotes: 1