将 JSON 反序列化为 Asp.Net Web API 中的派生类型 [英] Deserialising JSON to derived types in Asp.Net Web API

查看:18
本文介绍了将 JSON 反序列化为 Asp.Net Web API 中的派生类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

public 结果 Post([ModelBinder(typeof(CustomModelBinder))]MyClass 模型);

'MyClass',作为参数给出是一个抽象类.我希望根据传递的 json 类型,实例化正确的继承类.

为了实现它,我正在尝试实现自定义绑定器.问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中的原始 JSON(或者更好的某种序列化).

我明白了:

  • actionContext.Request.Content

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

解决方案

您不需要自定义模型绑定器.您也不需要处理请求管道.

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

我以此作为我自己解决同一问题的基础.

从该 SO 中引用的 JsonCreationConverter 开始(稍微修改以解决响应中类型序列化的问题):

公共抽象类 JsonCreationConverter;: JsonConverter{///<总结>///这很重要,否则序列化会中断!///</总结>公共覆盖布尔 CanWrite{得到{返回假;}}///<总结>///创建一个objectType的实例,基于JSON对象中的属性///</总结>///<param name="objectType">预期的对象类型</param>/// JSON 对象的内容///反序列化</param>///<returns></returns>protected abstract T Create(Type objectType, JObject jObject);public override bool CanConvert(Type objectType){返回 typeof(T).IsAssignableFrom(objectType);}公共覆盖对象 ReadJson(JsonReader reader, Type objectType,对象现有值,JsonSerializer 序列化器){if (reader.TokenType == JsonToken.Null)返回空;//从流中加载 JObjectJObject jObject = JObject.Load(reader);//基于JObject创建目标对象T target = Create(objectType, jObject);//填充对象属性serializer.Populate(jObject.CreateReader(), 目标);返回目标;}public override void WriteJson(JsonWriter writer, object value,JsonSerializer 序列化器){抛出新的 NotImplementedException();}}

现在您可以使用 JsonConverterAttribute 注释您的类型,将 Json.Net 指向自定义转换器:

[JsonConverter(typeof(MyCustomConverter))]公共抽象类 BaseClass{私有类 MyCustomConverter : JsonCreationConverter{protected override BaseClass Create(Type objectType,Newtonsoft.Json.Linq.JObject jObject){//TODO:通过jObject读取原始JSON对象识别类型//例如.在这里,我正在阅读typename"属性:if("DerivedType".Equals(jObject.Value("typename"))){返回新派生类();}返回新的默认类();//现在基类的代码将填充返回的对象.}}}公共类派生类:基类{公共字符串 DerivedProperty { 获取;放;}}公共类 DefaultClass : BaseClass {公共字符串 DefaultProperty { 获取;放;}}

现在您可以使用基本类型作为参数:

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

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

此外 - 如果您正在编写一个合适的"API,即专门供第三方使用,而不仅仅是供您自己使用,并且您正在使用 Web API,那么您很可能希望利用 JSON/XML内容类型处理(至少).看看您在尝试编写易于使用的文档(针对 XML 和 JSON 格式以不同方式指代所有 API 类型)方面取得了多大进展.

通过覆盖 JSON.Net 对类型名称的理解方式,您可以将两者结合起来,完全根据喜好为您的调用者选择 XML/JSON,而不是因为类型名称在一个或多个中更容易记住另一个.

I'm calling a method of my WebAPI sending a JSON that I would like to match (or bind) with a model.

In the controller I have a method like:

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

'MyClass', which is given as a parameter is an abstract class. I would like that at, depending of the type of json passed, the correct inherited class is instantiated.

To achieve it, I'm trying to implement a custom binder. The problem is that (I don't know if it's very basic but I can't find anything) I don't know how to retrieve the raw JSON (or better, some kind of serialization) that comes in the request.

I see:

  • actionContext.Request.Content

But all methods are exposed as async. I don't know who this fits with passing the generate model to the controller method...

解决方案

You don't need a custom model binder. Nor do you need to muck about with the request pipeline.

Take a look at this other SO: How to implement custom JsonConverter in JSON.NET to deserialize a List of base class objects?.

I used this as the basis for my own solution to the same problem.

Starting off with the JsonCreationConverter<T> referenced in that SO (slightly modified to fix issues with serialization of types in responses):

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

And now you can annotate your type with the JsonConverterAttribute, pointing Json.Net to a custom converter:

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

Now you can use the base type as a parameter:

public Result Post(BaseClass arg) {

}

And if we were to post:

{ typename: 'DerivedType', DerivedProperty: 'hello' }

Then arg would be an instance of the DerivedClass, but if we posted:

{ DefaultProperty: 'world' }

Then you'd get an instance of the DefaultClass.

EDIT - Why I prefer this method to TypeNameHandling.Auto/All

I do believe that using the TypeNameHandling.Auto/All espoused by JotaBe is not always the ideal solution. It might well be in this case - but personally I won't do it unless:

  • My API is only ever going to be used by me or my team
  • I don't care about having a dual XML-compatible endpoint

When Json.Net TypeNameHandling.Auto or All are used, your web server will start sending out type names in the format MyNamespace.MyType, MyAssemblyName.

I have said in comments that I think this is a security concern. Mention was made of this in some documentation I read from Microsoft. It's not mentioned any more, it seems, however I still feel it's a valid concern. I don't ever want to expose namespace-qualified type names and assembly names to the outside world. It's increasing my attack surface. So, yes, I can not have Object properties/parameters my API types, but who's to say the rest of my site is completely hole-free? Who's to say a future endpoint doesn't expose the ability to exploit type names? Why take that chance just because it's easier?

Also - if you are writing a 'proper' API, i.e. specifically for consumption by third-parties and not just for yourself, and you're using Web API, then you're most likely looking to leverage the JSON/XML content-type handling (as a minimum). See how far you get trying to write documentation that's easy to consume, which refers to all your API types differently for XML and JSON formats.

By overriding how JSON.Net understands the type names, you can bring the two into line, making the choice between XML/JSON for your caller purely based on taste, rather than because the type names are easier to remember in one or the other.

这篇关于将 JSON 反序列化为 Asp.Net Web API 中的派生类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆