Raphael
Raphael

Reputation: 127

Using inherited classes in .NET web API POST/PUT method

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

Answers (2)

itminus
itminus

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

Pouya Moradian
Pouya Moradian

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

Related Questions