Reputation: 127
I can't figure out how I can work with inherited classes in an web API controller. I have to create only one API controller to create and update inherited objects in the database.
Similar to my models (to all these models exists a Dto):
public class Animal
{
public virtual string Name {get; set;} // e.g. Harry
public virtual string Type {get; set;} // e.g. Dog
}
public class AnimalDto
{
public string Name;
public Type Type;
}
public class Dog : Animal
{
public virtual bool CanBark {get; set;} // e.g. true
}
public class Cat : Animal
{
public virtual bool CanMiau {get; set;}
}
I already tried to use the json in the controller. But there was the json always null
[HttpPost]
public ActionResult Post([FromBody]JObject json)
{
// idk what's going here?!
}
My controller now, but that cuts off all attributes of Dog or Cat model
[HttpPost]
public ActionResult Post([FromBody]AnimalDto animal)
{
// idk what's going here?!
}
I'm using .NET Core 2.0
Any ideas? Thanks!
EDIT
What if I want to do this dynamically? Something like:
var animal = json.ToObject<Animal>();
var actualAnimal = json.ToObject<typeof(animal.Type)>();
How can I do this?
Upvotes: 10
Views: 7579
Reputation: 25350
I already tried to use the json in the controller. But there was the json always null
The reason why you always get null
is that you didn't declare the correct Type
. You're not supposed to receive a string
type of animal via [FromBody]
.
[HttpPost]public ActionResult Post([FromBody]string json)public ActionResult Post([FromBody]Dog dog) { // now you get the dog }
As a side note, if you don't care the type at all, one approach is to declare a dynamic
type:
[HttpPost]
public IActionResult Post([FromBody] dynamic json)
{
return new JsonResult(json);
}
In this scenario, the dynamic type is JObject
, you could cast them to any type as you like:
public IActionResult Post(/*[ModelBinder(typeof(AnimalModelBinder))]*/[FromBody] JObject json)
{
var animal=json.ToObject<Animal>();
var dog = json.ToObject<Dog>();
return new JsonResult(json);
}
My controller now, but that cuts off all attributes of Dog or Cat model
If you would like to use the AnimalDto
, you should make the properties accessible:
public class AnimalDto {public string Name;public string Name{get;set;}public string Type;public string Type{get;set;} }
[Edit]
What if I want to do this dynamically? Something like:
var animal = json.ToObject<Animal>(); var actualAnimal = json.ToObject<typeof(animal.Type)>();
If we want to cast the json to some particular type, we must firstly know what the target Type
is . But your animal.Type
property is a type of String
, and that might be a by-convention string. If this Type
property is exactly the class name without namespace , e.g.
Cat.Type
must equals Cat
Dog.Type
must equals Dog
Fish.Type
must equals Fish
then you can you use Type.GetType()
to resolve the target type. For example :
// typename : the Animal.Type property
// ns : the namespace string
private Type ResolveAnimalType(string typename,string ns){
if(string.IsNullOrEmpty(ns)){ ns = "App.Models";}
typename= $"{ns}.{typename}";
var type=Type.GetType(
typename,
assemblyResolver:null, // if you would like load from other assembly, custom this resovler
typeResolver: (a,t,ignore)=> a == null ?
Type.GetType(t, false, ignore) :
a.GetType(t, false, ignore),
throwOnError:true,
ignoreCase:false
);
return type;
}
Anyway, you could custom the way we resolve the target Type
according to your own needs. And then cast the json object to your wanted Type
by method ToObject<T>()
:
[HttpPost]
public IActionResult Post([FromBody] JObject json)
{
var typename = json.Value<string>("type");
if(String.IsNullOrEmpty(typename))
ModelState.AddModelError("json","any animal must provide a `type` property");
// resolve the target type
var t= ResolveAnimalType(typename,null);
// get the method of `.ToObject<T>()`
MethodInfo mi= typeof(JObject)
.GetMethods(BindingFlags.Public|BindingFlags.Instance)
.Where(m=> m.Name=="ToObject" && m.GetParameters().Length==0 && m.IsGenericMethod )
.FirstOrDefault()
?.MakeGenericMethod(t); // ToObject<UnknownAnimialType>()
var animal = mi?.Invoke(json,null);
return new JsonResult(animal);
}
Upvotes: 5
Reputation: 93
You haven't mentioned that your are using either .NET Core or .NET Framework, but the main reason of this kind of behavior is model binding module which maps data from HTTP requests to action method parameters. I think you'd better to develop your own custom model binder and use it instead of the default one.
Here you can read more about custom model binding in asp .NET Core
Here you can read more about custom model binding in asp .NET MVC
Upvotes: 0