C# 在Asp.NETWebAPI中将JSON反序列化为派生类型
我正在调用WebAPI的一个方法,发送一个我希望与模型匹配(或绑定)的JSON 在控制器中,我有如下方法:C# 在Asp.NETWebAPI中将JSON反序列化为派生类型,c#,asp.net-mvc,asp.net-web-api,C#,Asp.net Mvc,Asp.net Web Api,我正在调用WebAPI的一个方法,发送一个我希望与模型匹配(或绑定)的JSON 在控制器中,我有如下方法: public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model); 作为参数提供的“MyClass”是一个抽象类。我希望根据传递的json类型,实例化正确的继承类 为了实现它,我正在尝试实现一个自定义绑定器。问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中的原始JSON(或者更好,
public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);
作为参数提供的“MyClass”是一个抽象类。我希望根据传递的json类型,实例化正确的继承类
为了实现它,我正在尝试实现一个自定义绑定器。问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中的原始JSON(或者更好,某种序列化)
我明白了:
- actionContext.Request.Content
但所有方法都以异步方式公开。我不知道将生成模型传递给控制器方法适合谁…您可以正常调用异步方法,您的执行将被暂停,直到方法返回,并且您可以以标准方式返回模型。就这样打个电话吧:
string jsonContent = await actionContext.Request.Content.ReadAsStringAsync();
它将为您提供原始JSON。您不需要自定义模型绑定器。你也不需要在请求管道上乱搞 看看另一个,所以: 我用这个作为我自己解决同一问题的基础 从中引用的
JsonCreationConverter
开始(稍作修改以修复响应中类型序列化的问题):
现在,可以将基类型用作参数:
public Result Post(BaseClass arg) {
}
如果我们要发布:
{ typename: 'DerivedType', DerivedProperty: 'hello' }
然后,arg
将是DerivedClass
的一个实例,但如果我们发布:
{ DefaultProperty: 'world' }
然后您将得到DefaultClass
的一个实例
编辑-为什么我喜欢此方法而不是typenameholling.Auto/All
我相信使用JotaBe支持的TypeNameHandling.Auto/All
并不总是理想的解决方案。在这种情况下很可能是这样,但就我个人而言,除非:
- 我的API只供我或我的团队使用
- 我不在乎有一个双XML兼容的端点
typenameholling.Auto
或All
时,web服务器将开始以MyNamespace.MyType、MyAssemblyName
的格式发送类型名称
我在评论中说过,我认为这是一个安全问题。在我从微软读到的一些文档中提到了这一点。它似乎不再被提及,但我仍然觉得这是一个合理的担忧。我不想向外界公开命名空间限定的类型名和程序集名。这增加了我的攻击面。所以,是的,我不能让Object
properties/parameters作为我的API类型,但是谁能说我的站点的其余部分完全没有漏洞呢?谁说未来的端点不会暴露利用类型名的能力?为什么仅仅因为比较容易就要冒这个险呢
另外,如果您正在编写一个“适当的”API,即专门供第三方使用,而不仅仅是供您自己使用,并且您正在使用Web API,那么您最有可能希望利用JSON/XML内容类型处理(至少)。看看您在尝试编写易于使用的文档方面取得了多大进展,这些文档以不同的方式引用XML和JSON格式的所有API类型
通过覆盖JSON.Net理解类型名的方式,您可以将两者结合起来,使调用方在XML/JSON之间的选择完全基于品味,而不是因为类型名在其中一个中更容易记住。您不需要自己实现它。JSON.NET对它有本机支持 您必须为JSON格式化程序指定以下内容(在
global.asax
application start事件中):
如果指定了Auto
,就像上面的示例一样,参数将反序列化为对象的$type
属性中指定的类型。如果缺少$type
属性,它将被反序列化为参数的类型。因此,在传递派生类型的参数时,只需指定类型。(这是最灵活的选择)
例如,如果将此参数传递给Web API操作:
var param = {
$type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
... // object properties
};
参数将反序列化为MyNamespace.MyType
class的对象
这也适用于子属性,也就是说,您可以有这样一个对象,它指定内部属性是给定类型的
var param = {
myTypedProperty: {
$type: `...`
...
};
在这里你可以看到一张照片
注意
你不需要用阁楼装饰任何东西,也不需要做任何其他定制。它将在不更改Web API代码的情况下工作
重要提示
。如果没有,它将被忽略
与自定义JsonConverter/JsonConverterAttribute的比较
我正在比较本机解决方案
要实现JsonConverter
/JsonConverterAttribute
:
- 您需要实现一个自定义的
,以及一个自定义的JsonConverter
JsonConverterAttribute
- 您需要使用属性来标记参数
- 您需要事先知道参数的可能类型
- 每当类型或属性发生更改时,您需要实现或更改
的实现JsonConverter
- 有一个代码气味,指示预期的属性名称
- 您没有实现可用于任何类型的泛型
- 你在重新发明轮子
对象
),否则不会有获取错误类型实例的风险:JSON.NET本机解决方案仅实例化参数类型的对象,或从参数类型派生的类型(如果不是,则获取null
)
这些是
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
.Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;
var param = {
$type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
... // object properties
};
var param = {
myTypedProperty: {
$type: `...`
...
};
public class InheritanceSerializationBinder : DefaultSerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
switch (typeName)
{
case "parent[]": return typeof(Class1[]);
case "parent": return typeof(Class1);
case "child[]": return typeof(Class2[]);
case "child": return typeof(Class2);
default: return base.BindToType(assemblyName, typeName);
}
}
}
var config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings { Binder = new InheritanceSerializationBinder() };