不要使用dynamic。我真的有点惊讶,它的工作原理。尽管没有迹象表明您已经实际测试过此代码,但可能还没有。 modelbinder 需要知道要绑定的具体类型,以便它可以确定如何将值映射到目标实例。如果没有强类型,它只能将所有内容都变成字符串,因为这就是它在请求正文中的方式。
无论如何,对于这样的事情,正确的方法是使用视图模型。您的视图模型应该包含所有各种可能的派生类型的所有属性。同样,modelbinder 需要这些来确定如何将请求正文中的数据映射过来,因此如果属性不存在,它将简单地丢弃关联的数据。
这也是为什么不能简单地使用基类的原因。如果这是一个正常的方法,你可以这样做:
public IActionResult Create([FromBody]User data)
然后,在内部,您可以使用模式匹配或类似的方法来转换为正确的派生类型。这是可行的,因为最终,内存中的对象实际上是UserA 之类的实例,而您只是将其向上转换为User。因此,您始终可以将其转换回 UserA。但是,动作不同。来自请求的内容是不是一个对象实例。 modelbinder 用于通过检查它需要绑定到的参数来从中创建一个对象实例。如果该参数的类型为User,那么它将填充User 上的属性,并丢弃其他所有内容。结果,内存中的对象只是User,并且没有办法转换为UserA 之类的东西——至少就UserA 的实例实际发布的所有值而言对象。
这让我们回到了视图模型:
public class UserViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string PropA { get; set; }
public string PropB { get; set; }
}
然后,让您的操作接受它作为参数:
public IActionResult Create([FromBody]UserViewModel data)
然后,里面:
if (!string.IsNullOrWhiteSpace(data.PropA))
{
// UserA was posted, map data to an instance of UserA
}
UserB 也是如此。如果您愿意,您还可以随数据发布一个显式的“类型”并打开它以实例化正确的类型。由你决定。为了减少代码重复,您可以实例化正确的类型,但将其存储在类型为User 的变量中。然后,如果您需要返回正确的类型,可以使用模式匹配:
User user;
switch (data.Type)
{
case "UserA":
user = new UserA
{
Id = data.Id,
Name = data.Name,
PropA = data.PropA
};
break;
// etc.
default:
user = new User
{
Id = data.Id,
Name = data.Name
};
break;
}
然后:
switch (user)
{
case UserA userA:
// do something specific with `userA`
// etc.
}
或者:
if (user is UserA userA)
{
// do something with `userA`
}