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

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

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

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

'MyClass',作为参数给出的是一个抽象类。 我希望在,根据传递的JSON的types,正确的inheritance类被实例化。

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

我懂了:

  • actionContext.Request.Content

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

非常感谢!

您不需要自定义模型联编程序。 你也不需要在请求pipe道上搞砸。

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

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

从该SO中引用的JsonCreationConverter<T>开始(稍作修改以解决响应中的types序列化问题):

 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注释您的types,将JsonConverterAttribute指向自定义转换器:

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

现在,您可以使用基本types作为参数:

 public Result Post(BaseClass arg) { } 

如果我们要发布:

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

然后arg将是DerivedClass一个实例,但如果我们发布:

 { DefaultProperty: 'world' } 

然后你会得到一个DefaultClass的实例。

编辑 – 为什么我喜欢这种方法TypeNameHandling.Auto/All

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

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

当使用Json.Net TypeNameHandling.AutoAll ,Web服务器将开始以MyNamespace.MyType, MyAssemblyName格式发送types名称。

我在评论中说过,我认为这是一个安全问题。 在我从微软读到的一些文档中提到了这一点。 现在不再提起来了,但是我仍然觉得这是一个值得关注的问题。 我不想公开命名空间限定的types名称和程序集名称到外面的世界。 这是增加我的攻击面。 所以,是的,我不能有我的APItypes的Object属性/参数,但谁说我的网站的其余部分是完全无孔? 谁能说未来的terminal不会暴露利用types名称的能力? 为什么抓住这个机会,因为它更容易?

此外,如果您正在编写一个“正确的”API,即专门供第三方使用,而不仅仅是为自己使用,并且您正在使用Web API,那么您最有可能利用JSON / XML内容types处理(至less)。 看看你试图编写容易使用的文档有多远,XML和JSON格式的所有APItypes都是不同的。

通过重写JSON.Net如何理解types名称,可以将两者联系起来,使纯粹基于口味的调用者在XML / JSON之间进行select,而不是因为types名称更容易记住。

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

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

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

如果你指定Auto ,就像在上面的例子中那样,参数将被反序列化为在对象的$type属性中指定的$type 。 如果缺less$type属性,它将被反序列化到参数的types。 所以你只需要在派生types的parameter passing时指定types。 (这是最灵活的select)。

例如,如果您将此parameter passing给Web API操作:

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

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

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

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

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

至less从JSON.NET 4发布以来,这是有效的 。

注意

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

重要的提示

$types必须是JSON序列化对象的第一个属性 。 如果不是,它将被忽略。

与定制JsonConverter / JsonConverterAttribute比较

我将本机解决scheme与此答案进行比较。

要实现JsonConverter / JsonConverterAttribute

  • 你需要实现一个自定义的JsonConverter和一个自定义的JsonConverterAttribute
  • 您需要使用属性来标记参数
  • 您需要事先知道参数所需的可能types
  • 您需要实现或更改JsonConverter的实现,只要您的types或属性发生更改
  • 有一个魔法string的代码气味,以指示预期的属性名称
  • 你没有实现一些可以用于任何types的generics
  • 你正在重新发明轮子

在答案的作者有一个关于安全性的评论。 除非你做了一些错误的事情(比如接受一个过于generics的参数types,比如Object ),那么就没有获得错误types实例的风险:JSON.NET原生解决scheme只实例化参数types的对象,或者派生types从它(如果没有,你会得到null )。

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

  • 你不需要实现任何东西(你只需要在应用程序中configurationTypeNameHandling
  • 您不需要在您的操作参数中使用属性
  • 您不需要事先知道可能的参数types:您只需要知道基本types,并在参数中指定它(可以是抽象types,使多态性更明显)
  • 该解决scheme适用于大多数情况下(1)而不改变任何东西
  • 这个解决scheme经过广泛的testing和优化
  • 你不需要魔术string
  • 该实现是通用的,并将接受任何派生types

(1):如果你想接收不是从同一个基typesinheritance的参数值,这是行不通的,但我看不出这样做的意义

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

为什么使用自定义JsonConverter / JsonConverterAttribute

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

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

您可以正常地调用asynchronous方法,您的执行将被简单地挂起,直到方法返回,您可以以标准方式返回模型。 只要打个电话就可以了

 string jsonContent = await actionContext.Request.Content.ReadAsStringAsync(); 

它会给你原始的JSON。

我为此创build了一个Helper方法:

只需传递一个请求对象,它会给你请求的身体。

看看下面的function:

  public static string GetRequestContent(HttpRequestMessage request) { string rawRequest = string.Empty; try { using (var stream = new StreamReader(request.Content.ReadAsStreamAsync().Result)) { stream.BaseStream.Position = 0; rawRequest = stream.ReadToEnd(); } } catch (Exception ex){ throw; } return rawRequest; } 

用法

 string baseRequestContent = HelperClassName.GetRequestContent(request); 

注意:HelperClassName将是你的助手/服务类的名字。