JSON不适用于Web Service中的类型化类

时间:2011-03-10 13:42:49

标签: javascript ajax web-services json asmx

我正在上课,如下所示:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

[DataContract()]
public class TestCol : List<Test> { }

[DataContract()]
public class MainTest
{
    public TestCol Components { get; set; }
}

[DataContract()]
public class Test
{
    public Test() { }
    public String Name { get; set; }
}

使用以下webmethod的web服务:

[WebMethod]
public String Test(MainTest input)
{
    String rtrn = String.Empty;
    foreach (Test test in input.Components)
        rtrn += test.Name;
    return rtrn;
}

AJAX使用以下方法调用它:

var Test = {};
Test.Name = "Test";

var MainTest = {};
MainTest.Components = [];
MainTest.Components.push(Test);

$.ajax({
    type: "POST",
    url: "WebService/WSTest.asmx/Test",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    data: JSON.stringify({
        "input": MainTest 
    }),
    success: function(data, textStatus) {
        console.log("success");
    },
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        window.console && console.log && console.log(XMLHttpRequest.responseText + " || " + textStatus + " || " + errorThrown);
    }
});

执行AJAX调用时,它将返回错误。我发现错误是类型为TestCol的类,它没有属性。 现在我找到了2个需要更改C#类的解决方案:

  1. 删除TestCol类并将Components属性更改为List<Test>数据类型:

    [DataContract()]
    public class MainTest
    {
        public List<Test> Components { get; set; }
    }
    
    [DataContract()]
    public class Test
    {
        public Test() { }
        public String Name { get; set; }
    }
    
  2. 或者向TestCol类添加一个额外的属性并更改web方法:

    [DataContract()]
    public class TestCol : List<Test>
    {
        public List<Test> Components { get; set; }
    }
    
    [DataContract()]
    public class MainTest
    {
        public TestCol Components { get; set; }
    }
    
    [DataContract()]
    public class Test
    {
        public Test() { }
        public String Name { get; set; }
    }
    

    &安培;

    [WebMethod]
    public String Test(MainTest input)
    {
        String rtrn = String.Empty;
        foreach (Test test in input.Components.Components)
                rtrn += test.Name;
        return rtrn;
    }
    
  3. 两种解决方案都需要更改C#类,我不愿意这样做,因为其他代码依赖于它。 有没有人知道这个问题的解决方案?

    编辑:我上传了一个测试解决方案,其中包含以上代码:http://jeroenvanwarmerdam.nl/content/temp/JSONtoClassWebservice.zip

6 个答案:

答案 0 :(得分:1)

因此,此解决方案将List更改为Object而不是Test。我希望改变尽可能少的代码(我不喜欢在foreach循环中进行强制转换)。下面的代码通过两个函数添加和前面提到的继承更改来实现。

public class TestCol : List<object>
{
    public new IEnumerator<Test> GetEnumerator()
    {
        return this.ConvertAll<Test>(
            dict => ConvertDictionaryTo<Test>(
                       (Dictionary<string, object>) dict
                    )
        ).GetEnumerator();
    }

    private T ConvertDictionaryTo<T>(IDictionary<string, object> dictionary) where T : new()
    {
        Type type = typeof(T);
        T ret = new T();

        foreach (var keyValue in dictionary)
        {
            type.GetProperty(keyValue.Key).SetValue(ret, keyValue.Value, null);
        }

        return ret;
    }
}

转换功能礼貌TurBas Mapping object to dictionary and vice versa

答案 1 :(得分:0)

嗯,这在网络方法中不起作用?

  foreach (Test test in input.Components.TestCol)

在下面评论,这是否有效?

  foreach (Test test in (List<Test>)input.Components.TestCol)

它应该可以工作,因为可以枚举一个类......

答案 2 :(得分:0)

如果您期待JSON,则需要返回JSON。 检查System.Web.Script.Serialization.JavaScriptSerializer http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx

答案 3 :(得分:0)

如果您使用ASMX服务,JavaScriptSerializer将负责数据转换,而不是DataContractJsonSerializer。因此,您使用的所有DataContract属性都无效

您写的是public class TestCol : List<Test> { }等类对JavaScriptSerializer不利,但List<Test>作为属性(public class MainTest { public List<Test> Components { get; set; }})的类没有问题。

所以我建议将代码简化为以下内容。用作参数的类可以定义为

public class Test {
    public String Name { get; set; }
}

public class MainTest {
    public List<Test> Components { get; set; }
}

WebMethod Test

[WebMethod]
public String Test(MainTest input)
{
    StringBuilder rtrn = new StringBuilder();
    foreach (Test test in input.Components) {
        rtrn.AppendLine (test.Name);
    }
    return rtrn.ToString ();
}

和ajax调用可以是

var tests = {
    Components: [
        {Name:"Test1"},
        {Name:"Test2"},
        {Name:"Test3"}
    ]
};

$.ajax({
    type: "POST",
    url: "WebService1.asmx/Test",
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    data: JSON.stringify({
        "input": tests
    }),
    success: function (data, textStatus) {
        alert("success:\n" + data.d);
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) {
        alert(XMLHttpRequest.responseText+" || "+textStatus+" || "+errorThrown);
    }
});

如何看待所有内容将非常简单并且有效。有关如何发送复杂数据的详细信息,建议您阅读another answerthis

答案 4 :(得分:0)

您似乎在使用ASMX(而不是WCF),因为您在所有公共属性上省略了[DataMember]属性,但仍然被序列化。 WCF是“选择加入”,所以你不应该看到任何正确的序列化。

因此,所有[DataContract]属性都无用。

如果您使用ScriptManger并输出JSON,ASMX默认使用JavaScriptSerializer。 JavaScriptSerializer是“选择退出”(这意味着所有公共属性都会自动序列化,除非标有[ScriptIgnoreAttribute])。

JavaScriptSerializer支持序列化List&lt;&gt ;.您不应该在序列化TestCol属性时遇到问题,因为JavaScriptSerializer自动支持序列化实现IEnumerable(但不是IDictionary)的所有类型 - 其中包括List&lt;&gt; - 进入JSON数组。

您的错误似乎是JavaScriptSerializer无法正确处理来自List&lt;&gt;的继承的类(或从实现IEnumerable的类)。在第一次解决方法中,您删除了从List&lt;&gt;继承的类。在第二种解决方法中,您跳过了基类的所有功能,但重新实现了List&lt;&gt;在一个财产。

您的JSON帖子数据目前如下:

{ Components: [
    { Name:"foo" },
    { Name:"bar" },
        :
] }

但是,序列化程序中有一个额外级别或重定向(继承自List<Test> - &gt; TestCol)。序列化程序可能正在寻找:

{ Components: {
    Items: [
        { Name:"foo" },
        { Name:"bar" },
            :
    ] }
}

因为您基本上是序列化List&lt;&gt;的“Items”属性。因此,您的JSON帖子数据只是将Test对象送到错误的位置,而您的TestCol Components属性最终为空。

我建议您添加一个Web服务方法来输出测试MainTest对象以查看它序列化的内容。您可能会发现它会增加一个级别。

答案 5 :(得分:0)

JavaScriptSerializer序列化:IEnumerable - &gt; JavaScript数组

当使用JavaScriptSerializer时,它会自动转换IEnumerable(不带IDictionary)类型 - 它涵盖List&lt;&gt;或者从它衍生的任何东西 - 变成一个数组。

反序列化:JavaScript数组 - &gt; IEnumerable - &gt;集合对象

现在,在JSON的反序列化之后,JavaScriptSerializer必须获取数组,创建一个IEnumerable,然后通过将该IEnumerable传递给它的构造函数来为该字段创建一个对象。

通过构造函数构建集合对象

现在,对于List&lt;&gt;你有一个构造函数重载,它采用IEnumerable。因此,如果您将List<Test>作为组件的类型,则可以创建它。

未继承的构造函数

但是,TestCol NOT 有这样的构造函数!它与List<Test>而非TestCol(源自List<Test>)的原因是,在类之间继承的唯一是构造函数!

因此,JavaScriptSerializer没有任何方法可以从IEnumerable构造TestCol。所以它无声地失败了。

通过创建列表反序列化数组,然后转换为类型

现在,JavaScriptSerializer可能会尝试从此List<Test>创建IEnumerable<Test>,然后尝试将其转换为TestCol。

可能的解决方案

解决方案:尝试投入:

public TestCol () {}       // Need this when you have another constructor
public TestCol (IEnumerable<Test> list) : base(list) {}         // Constructor that takes an IEnumerable
public TestCol (IList<Test> list) : base(list) {}         // Constructor that takes an IList

作为TestCol的构造函数。

如果它仍然不起作用,请实施从List<Test>TestCol的显式类型转换。

public static explicit operator TestCol(IList<Test> list) { return new TestCol(list); }