在.NET Web API的POST/PUT方法中使用继承类

9

我无法弄清如何在Web API控制器中处理继承类。我必须创建仅一个API控制器来创建和更新数据库中的继承对象。

与我的模型类似(每个模型都有一个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;}
}

我已经尝试在控制器中使用JSON,但总是会出现JSON为null的情况。

[HttpPost]
public ActionResult Post([FromBody]JObject json)
{
    // idk what's going here?!
}

我的控制器现在可以工作,但是它会切断狗或猫模型的所有属性。

[HttpPost]
public ActionResult Post([FromBody]AnimalDto animal)
{
    // idk what's going here?!
}

我正在使用.NET Core 2.0。
有什么想法吗?谢谢!
编辑:如果我想动态执行此操作怎么办?例如:
var animal = json.ToObject<Animal>();
var actualAnimal = json.ToObject<typeof(animal.Type)>();

我该怎么做呢?


请同时展示AnimalDto的代码。 - Dimitar
@Dimitar 已经添加了它。 - Raphael
仅仅因为你可能有一个约定,即AnimalType属性必须是DogCat之一,并且这与实际使用的子类型相关联(我猜这就是你正在做的),那么你认为通用的JSON解析器会知道这一点吗? - Damien_The_Unbeliever
2个回答

5
我已经尝试在控制器中使用json,但json总是为null。
你总是得到null的原因是没有声明正确的类型。你不能通过[FromBody]接收一个string类型的动物。
附带说明一下,如果你根本不关心类型,一种方法是声明一个动态类型:
    [HttpPost]
    public ActionResult Post([FromBody]string json)
    public ActionResult Post([FromBody]Dog dog)
    {
        //现在你可以获得狗了
    }
[HttpPost]
public IActionResult Post([FromBody] dynamic json)
{
    return new JsonResult(json); 
}

在这种情况下,动态类型是 JObject,您可以将它们转换为任何类型,如您所愿:
public IActionResult Post(/*[ModelBinder(typeof(AnimalModelBinder))]*/[FromBody] JObject json)
{
    var animal=json.ToObject<Animal>();
    var dog = json.ToObject<Dog>();
    return new JsonResult(json); 
}

如果您现在使用的是控制器,但是这会削减Dog或Cat模型的所有属性。如果您想使用AnimalDto,您应该使属性可访问:
    public class AnimalDto
    {
        public string Name;
        public string Name{get;set;}
        public string Type;
        public string Type{get;set;}
    }
[编辑]

What if I want to do this dynamically? Something like:

var animal = json.ToObject<Animal>();
var actualAnimal = json.ToObject<typeof(animal.Type)>();
如果我们想将json转换为特定类型,首先必须知道目标“Type”是什么。但是您的“animal.Type”属性是一个“String”类型,并且可能是按约定命名的字符串。如果此“Type”属性恰好是不带命名空间的类名,例如:
  • Cat.Type必须等于Cat
  • Dog.Type必须等于Dog
  • Fish.Type必须等于Fish
然后您可以使用“Type.GetType()”来解析目标类型。例如:
// 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;
}

无论如何, 您都可以根据自己的需求自定义解析目标Type的方式。然后通过方法ToObject<T>()将JSON对象转换为所需的Type

[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); 
}

那个可行!但如果我想更动态地做这个怎么办?-> 请看我的编辑 - Raphael
1
@R.Müller 我已经更新了我的答案并创建了一个演示以确保它可以正常工作。如果您有任何问题,请随时告诉我。 - itminus
哥们儿,你真是我的英雄!非常非常非常非常感谢你!你简直救了我一命!:D 帮了我大忙! - Raphael

0

我该怎么做?你能给我上面那段代码的例子吗?(顺便提一下,这是.net core 2.0) - Raphael
当然,您可以在此处找到一些多态模型绑定器:https://www.tutorialdocs.com/article/webapi-data-binding.html - Pouya Moradian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接