一尘不染

将Json反序列化为Asp.Net Web API中的派生类型

c#

我正在调用WebAPI的方法,该方法发送一个我想与模型匹配(或绑定)的json。

在控制器中,我有一个类似的方法:

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

“ MyClass”,作为一个抽象类的参数给出。我想根据所传递的json的类型,实例化正确的继承类。

为了实现它,我正在尝试实现一个自定义的活页夹。问题是(我不知道它是否很基本,但是什么也找不到)我不知道如何检索请求中包含的原始Json(或更好的某种序列化)。

我知道了:

  • actionContext.Request.Content

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

非常感谢!


阅读 245

收藏
2020-05-19

共1个答案

一尘不染

您不需要自定义模型活页夹。您也不需要考虑请求管道。

看看另一个SO:如何在JSON.NET中实现自定义JsonConverter以反序列化基类对象列表?

我以此为基础来解决自己的问题。

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.Auto或时All,您的Web服务器将开始以格式发送类型名称MyNamespace.MyType, MyAssemblyName

我在评论中说过,我认为这是一个安全问题。我从Microsoft阅读的一些文档中提到了这一点。似乎没有再提及,但是我仍然觉得这是一个合理的问题。我
从来

不想向外界公开名称空间限定的类型名称和程序集名称。它增加了我的攻击面。因此,是的,我无法拥有ObjectAPI类型的属性/参数,但是谁能说我网站的其余部分完全没有漏洞呢?谁说未来的端点不会暴露利用类型名称的能力?为什么仅仅因为它更容易而抓住这次机会?

另外-如果您正在编写“适当的” API,即专门为第三方使用而不仅仅是为自己使用,并且正在使用Web API,那么您很可能希望利用JSON /
XML内容类型处理(至少)。了解您尝试编写易于使用的文档的程度,该文档针对XML和JSON格式对所有API类型的引用有所不同。

通过重写JSON.Net如何理解类型名称,您可以将二者结合在一起,纯粹根据喜好在调用方的XML /
JSON之间进行选择,而不是因为类型名称更容易在一个或另一个中记住。

2020-05-19