Jac*_*cob 59 c# asp.net-mvc asp.net-web-api
我正在调用我的WebAPI的方法,发送一个我希望与模型匹配(或绑定)的json.
在控制器中我有一个方法,如:
public Result Post([ModelBinder(typeof(CustomModelBinder))]MyClass model);
Run Code Online (Sandbox Code Playgroud)
'MyClass',作为参数给出的是一个抽象类.我想这样,根据传递的json的类型,实例化正确的继承类.
为了实现它,我正在尝试实现自定义绑定器.问题是(我不知道它是否非常基本,但我找不到任何东西)我不知道如何检索请求中的原始Json(或更好的,某种序列化).
我知道了:
但是所有方法都暴露为异步.我不知道这适合将生成模型传递给控制器方法...
非常感谢!
And*_*tan 91
您不需要自定义模型绑定器.你也不需要处理请求管道.
看看这个其他的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();
}
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以使用将JsonConverterAttributeJson.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; }
}
Run Code Online (Sandbox Code Playgroud)
现在您可以使用基类型作为参数:
public Result Post(BaseClass arg) {
}
Run Code Online (Sandbox Code Playgroud)
如果我们发布:
{ typename: 'DerivedType', DerivedProperty: 'hello' }
Run Code Online (Sandbox Code Playgroud)
然后arg将是一个实例DerivedClass,但如果我们发布:
{ DefaultProperty: 'world' }
Run Code Online (Sandbox Code Playgroud)
然后你会得到一个实例DefaultClass.
TypeNameHandling.Auto/All我相信使用TypeNameHandling.Auto/AllJotaBe所支持的并不总是理想的解决方案.在这种情况下很可能 - 但我个人不会这样做,除非:
当Json.Net TypeNameHandling.Auto或All使用,Web服务器将开始在格式发出类型名称MyNamespace.MyType, MyAssemblyName.
我在评论中说过,我认为这是一个安全问题.在我从微软阅读的一些文档中提到了这一点.它似乎没有被提及,但我仍然觉得这是一个有效的问题.我不以往任何时候都希望名称空间限定的类型名称和程序集名称暴露给外界.它增加了我的攻击面.所以,是的,我不能拥有Object我的API类型的属性/参数,但谁能说我的网站的其余部分完全没有空洞?谁说未来的端点没有公开利用类型名称的能力?为什么选择这个机会只是因为它更容易?
另外 - 如果您正在编写一个"正确的"API,即专门供第三方使用而不仅仅是为了您自己,并且您正在使用Web API,那么您很可能希望利用JSON/XML内容类型处理(至少).了解您尝试编写易于使用的文档的程度,它指的是XML和JSON格式的所有API类型.
通过重写JSON.Net如何理解类型名称,您可以将两者放在一起,使您的调用者之间的XML/JSON之间的选择完全基于品味,而不是因为类型名称在一个或另一个中更容易记住.
Jot*_*aBe 49
您不需要自己实现它.JSON.NET对它有本机支持.
您必须为JSON格式化程序指定所需的TypeNameHandling选项,如下所示(在global.asax应用程序启动事件中):
JsonSerializerSettings serializerSettings = GlobalConfiguration.Configuration
.Formatters.JsonFormatter.SerializerSettings;
serializerSettings.TypeNameHandling = TypeNameHandling.Auto;
Run Code Online (Sandbox Code Playgroud)
如果您指定Auto,就像在上面的示例中一样,参数将被反序列化为$type对象属性中指定的类型.如果$type缺少该属性,则将其反序列化为参数的类型.因此,您只需在传递派生类型的参数时指定类型.(这是最灵活的选择).
例如,如果将此参数传递给Web API操作:
var param = {
$type: 'MyNamespace.MyType, MyAssemblyName', // .NET fully qualified name
... // object properties
};
Run Code Online (Sandbox Code Playgroud)
该参数将被反序列化为MyNamespace.MyType类的对象.
这也适用于子属性,即,您可以拥有这样的对象,它指定内部属性是给定类型
var param = {
myTypedProperty: {
$type: `...`
...
};
Run Code Online (Sandbox Code Playgroud)
在这里,您可以看到TypeNameHandling.Auto的JSON.NET文档中的示例.
注意
您不需要使用attirbutes来装饰任何东西,也不需要进行任何其他自定义.它将在您的Web API代码中不做任何更改的情况下工作.
重要的提示
$ type必须是JSON序列化对象的第一个属性.如果没有,它将被忽略.
与CUSTOM JsonConverter/JsonConverterAttribute的比较
我正在将本机解决方案与此答案进行比较.
实现JsonConverter/ JsonConverterAttribute:
JsonConverter和自定义JsonConverterAttributeJsonConverter在类型或属性发生变化时实施或更改您的实现在答案的作者中有关于安全性的评论.除非你做错了(比如为你的参数接受过于通用的类型Object),否则不存在获取错误类型的实例的风险:JSON.NET本机解决方案只实例化参数类型的对象,或者派生自的类型它(如果没有,你得到null).
这些是JSON.NET本机解决方案的优点:
TypeNameHandling在应用程序中配置一次)(1):如果你想接收不从相同基类型继承的参数值,这将不起作用,但我认为这样做没有意义
所以我找不到任何缺点,并在JSON.NET解决方案上找到许多优势.
为什么使用CUSTOM JsonConverter/JsonConverterAttribute
这是一个很好的工作解决方案,允许自定义,可以修改或扩展以适应您的特定情况.
如果要执行本机解决方案无法执行的操作(如自定义类型名称)或根据可用属性名称推断参数类型,请使用适合您自己案例的此解决方案.另一个无法定制,无法满足您的需求.