如何在ASP.NETWebAPI中将JSON反序列化为派生类型?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (19)

我打电话给我的WebAPI发送一个我想匹配(或绑定)模型的json的方法。

在控制器中我有一个方法,如:

public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);

'MyClass',这个参数是一个抽象类。我希望在那里,根据json传递的类型,正确的继承类被实例化。

为了实现它,我试图实现一个自定义绑定。问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中包含的原始Json(或更好,某种序列化)。

我懂了:

  • actionContext.Request.Content

但是所有方法都以异步方式公开。我不知道这是否适合将生成模型传递给控制器​​方法。

提问于
用户回答回答于

不需要自定义模型绑定器。你也不需要搞清楚请求管道。

我用这个作为我自己解决同样问题的基础。

JsonCreationConverter<T>SO中的引用开始(略作修改以解决响应中的类型序列化问题):

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

我相信使用TypeNameHandling.Auto/AllJotaBe所支持的并不总是理想的解决方案。这可能是在这种情况下 - 但我个人不会这样做,除非:

  • 我的API 只会被我或我的团队使用
  • 我不在乎拥有一个兼容XML的端点

当使用Json.Net TypeNameHandling.AutoAll使用时,您的Web服务器将开始以格式发送类型名称MyNamespace.MyType, MyAssemblyName

我在评论中表示,我认为这是一个安全问题。在我从微软读到的一些文档中提到了这一点。它不再被提及,似乎,但我仍然认为这是一个有效的关注。我永远不想公开命名空间限定的类型名称和程序集名称给外部世界。它正在增加我的攻击面。所以,是的,我不能有Object属性/参数我的API类型,但是谁说我的网站的其余部分完全没有洞?谁说未来的终端不会暴露利用类型名称的能力?为什么只是因为它更容易就抓住这个机会?

另外 - 如果正在编写一个'正确的'API,即专门供第三方使用,而不仅仅是为自己使用,并且正在使用Web API,那么很可能会考虑利用JSON / XML内容类型处理(至少)。看看你试图编写容易使用的文档有多远,这对XML和JSON格式引用的所有API类型都是不同的。

通过重写JSON.Net如何理解类型名称,可以将两者结合在一起,为调用者纯粹根据口味选择XML / JSON,而不是因为类型名称更容易记住。

用户回答回答于

你不需要自己实现它。JSON.NET对它有本地支持。

必须为JSON格式化程序指定所需的TypeNameHandling选项,如下所示(在global.asax应用程序启动事件中):

JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
   .Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;

如果指定Auto(如上面的示例中所示),则参数将被反序列化为$type对象属性中指定的类型。如果该$type属性丢失,它将被反序列化为参数的类型。所以当你传递派生类型的参数时,你只需要指定类型。(这是最灵活的选择)。

例如,如果将此参数传递给Web API操作:

var param = {
    $type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
    ... // object properties
};

该参数将被反序列化为MyNamespace.MyType类的对象。

这也适用于子属性,也就是说,可以拥有一个像这样的对象,它指定内部属性是给定类型

var param = { 
   myTypedProperty: {
      $type: `...`
      ...
};

在这里你可以看到一个关于TypeNameHandling.Auto的JSON.NET文档的例子

至少自从JSON.NET 4发布以来,这起作用

注意

你不需要用attirbutes来装饰任何东西,或者做任何其他的定制。它将在Web API代码没有任何改变的情况下工作。

与定制JsonConverter / JsonConverterAttribute比较

要实现JsonConverter/ JsonConverterAttribute

  • 你需要实现一个自定义JsonConverter和一个自定义JsonConverterAttribute
  • 需要使用属性来标记参数
  • 需要事先知道参数的可能类型
  • 需要实现或更改JsonConverter您的类型或属性更改时的实现
  • 有一个字符串的代码,以指示预期的属性名称
  • 你没有实现可用于任何类型的通用类
  • 你正在重新发明轮子

这些是JSON.NET原生解决方案的优点:

  • 你不需要实现任何东西(你只需要TypeNameHandling在应用中配置一次)
  • 不需要在动作参数中使用属性
  • 不需要事先知道可能的参数类型:您只需要知道基本类型,并在参数中指定它(可以是抽象类型,使多态性更明显)
  • 该解决方案适用于大多数情况(1)而不更改任何内容
  • 此解决方案经过广泛测试和优化
  • 你不需要魔术字符串
  • 该实现是通用的,并且将接受任何派生类型

(1):如果你想接收不是从同一个基类型继承的参数值,这是行不通的,但我没有看到这么做的意义

所以我找不到任何缺点,并在JSON.NET解决方案上找到很多优势。

为什么使用自定义JsonConverter / JsonConverterAttribute

这是一个很好的工作解决方案,允许定制,可以修改或扩展,以适应您的特定情况。

如果你想做一些本地解决方案无法做到的事情,比如自定义类型名称,或者根据可用的属性名称推断参数的类型,那么请使用适合你自己的情况的解决方案。另一个不能定制,并且不能满足您的需求。

扫码关注云+社区