您不需要自定义模型绑定器。您也不需要处理请求管道。
看看另一个 SO:How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?。
我以此作为我自己解决同一问题的基础。
从该 SO 中引用的 JsonCreationConverter<T> 开始(稍作修改以解决响应中类型序列化的问题):
public abstract class JsonCreationConverter<T> : JsonConverter
{
/// <summary>
/// this is very important, otherwise serialization breaks!
/// </summary>
public override bool CanWrite
{
get
{
return false;
}
}
/// <summary>
/// Create an instance of objectType, based properties in the JSON object
/// </summary>
/// <param name="objectType">type of object expected</param>
/// <param name="jObject">contents of JSON object that will be
/// deserialized</param>
/// <returns></returns>
protected abstract T Create(Type objectType, JObject jObject);
public override bool CanConvert(Type objectType)
{
return typeof(T).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
// Load JObject from stream
JObject jObject = JObject.Load(reader);
// Create target object based on JObject
T target = Create(objectType, jObject);
// Populate the object properties
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
现在您可以使用 JsonConverterAttribute 注释您的类型,将 Json.Net 指向自定义转换器:
[JsonConverter(typeof(MyCustomConverter))]
public abstract class BaseClass{
private class MyCustomConverter : JsonCreationConverter<BaseClass>
{
protected override BaseClass Create(Type objectType,
Newtonsoft.Json.Linq.JObject jObject)
{
//TODO: read the raw JSON object through jObject to identify the type
//e.g. here I'm reading a 'typename' property:
if("DerivedType".Equals(jObject.Value<string>("typename")))
{
return new DerivedClass();
}
return new DefaultClass();
//now the base class' code will populate the returned object.
}
}
}
public class DerivedClass : BaseClass {
public string DerivedProperty { get; set; }
}
public class DefaultClass : BaseClass {
public string DefaultProperty { get; set; }
}
现在您可以使用基本类型作为参数:
public Result Post(BaseClass arg) {
}
如果我们要发帖:
{ typename: 'DerivedType', DerivedProperty: 'hello' }
那么arg 将是DerivedClass 的一个实例,但如果我们发布:
{ DefaultProperty: 'world' }
然后你会得到一个DefaultClass 的实例。
编辑 - 为什么我更喜欢这种方法而不是 TypeNameHandling.Auto/All
我确实相信使用 JotaBe 支持的 TypeNameHandling.Auto/All 并不总是理想的解决方案。在这种情况下很可能是这样 - 但我个人不会这样做,除非:
- 我的 API只有会被我或我的团队使用
- 我不在乎拥有双 XML 兼容端点
当使用 Json.Net TypeNameHandling.Auto 或 All 时,您的 Web 服务器将开始以 MyNamespace.MyType, MyAssemblyName 格式发送类型名称。
我在 cmets 中说过,我认为这是一个安全问题。在我从 Microsoft 阅读的一些文档中提到了这一点。它似乎不再被提及,但我仍然觉得这是一个有效的担忧。我永远不想向外界公开命名空间限定的类型名称和程序集名称。它增加了我的攻击面。所以,是的,我的 API 类型不能有Object 属性/参数,但谁能说我的网站的其余部分完全没有漏洞?谁说未来的端点不会暴露利用类型名称的能力?为什么要抓住这个机会,因为它更容易?
另外 - 如果您正在编写一个“适当的”API,即专门供第三方使用,而不仅仅是供您自己使用,并且您正在使用 Web API,那么您很可能希望利用 JSON/XML内容类型处理(至少)。看看您在尝试编写易于使用的文档方面取得了多大的成就,这些文档针对 XML 和 JSON 格式以不同的方式引用您的所有 API 类型。
通过重写 JSON.Net 对类型名称的理解方式,您可以使两者保持一致,为您的调用者在 XML/JSON 之间进行选择完全基于口味,而不是因为类型名称更容易记住一个或另一个。