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

时间:2014-05-21 17:22:41

标签: c# asp.net asp.net-mvc json json.net

我在我的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,   Version = 4.0.0.0,Culture = neutral,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级后甚至无法工作,那么不知道为什么代码如此受欢迎?

2 个答案:

答案 0 :(得分:0)

太多开销。试试:

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答案以更改此行为。

答案 1 :(得分:0)

错误消息"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.cs下的Application_Start()中连接模型绑定器:

protected void Application_Start()
{
    // custom model binders
    ModelBinders.Binders.Add(typeof(Money), new MoneyModelBinder());
}

只要模型绑定过程在Money类型上运行,它就会调用此类并尝试进行转换。


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