带有 MVC4 的 Newtonsoft JSON 无法转换参数类型

Posted

技术标签:

【中文标题】带有 MVC4 的 Newtonsoft JSON 无法转换参数类型【英文标题】:Newtonsoft JSON with MVC4 failing to convert parameter types 【发布时间】:2014-07-10 11:56:33 【问题描述】:

我根据此处引用的示例在我的 MVC 中使用 Newtonsoft JSON:Setting the Default JSON Serializer in ASP.NET MVC。

我的数据契约类看起来像:

[DataContract]
public MyContractClass

    public MyContractClass()
    
        this.ThisPropertyFails = new List<ClassDefinedInAnotherAssembly>();
    

    [DataMember(EmitDefaultValue = false, Name = "thisPropertyIsFine")]
    public string ThisPropertyIsFine  get; set; 

    [DataMember(EmitDefaultValue = false, Name = "thisPropertyFails")]
    public IList<ClassDefinedInAnotherAssembly> ThisPropertyFails  get; internal set; 

我专门用于反序列化的代码如下所示:

public override IValueProvider GetValueProvider(ControllerContext controllerContext)

    if (controllerContext == null)
    
        throw new ArgumentNullException("controllerContext");                
    

    if (!controllerContext.HttpContext.Request.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
    
        return null;                
    

    var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream);
    var bodyText = reader.ReadToEnd();

    if (String.IsNullOrEmpty(bodyText))
    
        return null;
    
    else
    
        JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
        serializerSettings.Converters.Add(new StringEnumConverter());
        serializerSettings.Converters.Add(new ExpandoObjectConverter());

        DictionaryValueProvider<object> result = new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, serializerSettings), CultureInfo.CurrentCulture);
        return result;
    

    //return String.IsNullOrEmpty(bodyText) ? null : new DictionaryValueProvider<object>(JsonConvert.DeserializeObject<ExpandoObject>(bodyText, new ExpandoObjectConverter(), new StringEnumConverter()), CultureInfo.InvariantCulture);

但是,在 MVC 操作中,ModelState.IsValid 为 false,查看错误,我看到:

"参数从类型转换 'System.Collections.Generic.List`1[[System.Object, mscorlib, 版本=4.0.0.0,文化=中性,PublicKeyToken=b77a5c561934e089]]' 键入“OtherAssembly.ClassDefinedInAnotherAssembly”失败,因为 没有类型转换器可以在这些类型之间进行转换。"

有人知道这里发生了什么吗?同样的类适用于我的 WebApi 项目(在本例中为“OtherAssembly”)。

编辑#1

直接使用已知类型的代码确实有效。所以这与 ExpandoObject 下的属性有关。例如这段代码:

    JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
    serializerSettings.Converters.Add(new StringEnumConverter());
    serializerSettings.Converters.Add(new ExpandoObjectConverter());

    JsonSerializer serializer = JsonSerializer.Create(serializerSettings);

    using (StreamReader streamReader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
    
        using (JsonReader jsonReader = new JsonTextReader(streamReader))
        
            var resultAbc = serializer.Deserialize(jsonReader, typeof(MyContractClass));
        
    

工作得很好。

编辑#2

看来我不是唯一遇到此问题的人。任何使用 MVC 并使用经常引用的源代码来使用 Newtonsoft 的人都无法反序列化复杂的子属性: http://tech.pro/q/34/using-jsonnet-to-deserialize-incoming-json-in-aspnet-mvc

不知道为什么该代码在合约中的 1 级之后甚至都不起作用的情况下如此受欢迎?

【问题讨论】:

也许你应该扩展你自己的 JsonConverter 来明确转换你的 OtherAssembly.ClassDefinedInAnotherAssembly ? @Ksven 我可能可以做到这一点,但有数百甚至数千个课程,所以必须为所有课程都这样做似乎很愚蠢?这没有任何意义。 您有 数千个 类要序列化? @Bob。哈哈,我猜几千有点夸张,但我现在确实有 300 多个,而且还在增长(企业应用)。为所有这些都编写特定的转换器是不切实际的,我什至不应该这样做,因为这些完全相同的东西在 WebApi 和 Newtonsoft 中都可以正常工作。 希望您只是序列化相关数据而不是整个类,因为数据在这些类中......我一直在使用 MyObjectType myObj = JsonConvert.DeserializeObject&lt;MyObjectType&gt;(temp); 直接反序列化对象 【参考方案1】:

开销太大。试试看:

public MyContractClass


    [JsonProperty("thisPropertyIsFine")]
    public string ThisPropertyIsFine  get; set; 

    [JsonProperty("thisPropertyFails")]
    public ClassDefinedInAnotherAssembly[] ThisPropertyFails  get; set; 

这样序列化和反序列化

/* as Bob mentioned */
var deserialized = JsonCovert.DeserializeObject<MyContractClass>(jsonString);
var serialized = JsonCovert.SerializeObject(myContractClassInstance);

Json.NET 不序列化非公共成员。请参阅 this 回答以更改此行为。

【讨论】:

根据 C# 中的 CA/SA 实践,拥有 IList 或具有内部集的数组是正常的。此外,JSON.NET 可以很好地使用它,因为它不需要 set 它,它需要做的就是调用 IEnumerator.Add,所以你错了。问题根本不在于这个。问题是自动反序列化来自不同程序集的复杂对象,这仍然是不可能的。【参考方案2】:

错误消息"The parameter conversion from type 'X' to type 'Y' failed because no type converter can convert between these types." 来自 ASP.Net 模型绑定过程,不是来自 Json.Net 的反序列化过程。

要修复此错误,您只需为出现问题的任何类型创建自定义模型绑定器 (ClassDefinedInAnotherAssembly)。这是我目前用于我的一种名为Money 的自定义类型的示例:

public class MoneyModelBinder : DefaultModelBinder

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    
        // get the result
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        Money value;
        // if the result is null OR we cannot successfully parse the value as our custom type then let the default model binding process attempt it using whatever the default for Money is
        return valueProviderResult == null || !Money.TryParse(valueProviderResult.AttemptedValue, out value) ? base.BindModel(controllerContext, bindingContext) : value;
    

TryParse 来自Money 类型:

public static bool TryParse(string value, out Money money)

    money = null;

    if (string.IsNullOrWhiteSpace(value))
        return false;

    decimal parsedValue;
    if (decimal.TryParse(value, out parsedValue) == false)
        return false;

    money = parsedValue;

    return true;

您在Global.asax.csApplication_Start() 下连接模型活页夹:

protected void Application_Start()

    // custom model binders
    ModelBinders.Binders.Add(typeof(Money), new MoneyModelBinder());

只要模型绑定过程遇到Money 类型,它就会调用这个类并尝试进行转换。


您可能希望暂时删除您的 Json.Net ValueProviderFactory,以便在您解决模型绑定问题时它不是一个因素。那里有很多 Json.Net ValueProviderFactorys。并非所有人都是平等的,并且可能会导致诸如未序列化字典/数组或在放弃之前未完全迭代对象整个层次结构等问题。

【讨论】:

以上是关于带有 MVC4 的 Newtonsoft JSON 无法转换参数类型的主要内容,如果未能解决你的问题,请参考以下文章

使用 Newtonsoft 解析带有嵌套和变量字典的 Json [重复]

NewtonSoft Json DeserializeObject 空 Guid 字段

FineUI中Newtonsoft.Json版本报错解决办法

“Newtonsoft.Json.JsonConvert”类型存在于“Newtonsoft.Json.dll”和“NuGetApi2.dll”中

Newtonsoft.Json(Json.net) 的使用

带有JsonIgnoreAttribute的属性的Json.NET JsonSerializationException