【问题标题】:JSON doesn't work with typed class in WebserviceJSON 不适用于 Web 服务中的类型化类
【发布时间】:2011-07-12 17:41:34
【问题描述】:

我正在上课,如下所示:

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&lt;Test&gt; 数据类型:

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

    [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;
    }
    

这两种解决方案都需要更改 C# 类,我不希望这样做,因为其他代码依赖于它。 有人知道这个问题的解决方案吗?

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

【问题讨论】:

  • 您知道DataContract 与 WCF 相关,而不是与 ASMX Web 服务相关吗?
  • 您能否提供有关您收到的错误的信息?我之前在空类上使用过 DataContract,没有任何问题。

标签: javascript ajax web-services json asmx


【解决方案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

【讨论】:

  • 我想这种转换也可能发生在getter/setter方法MainTest.Components中
  • 感谢迈克尔·吉列特。这几乎就是我想要的。我希望使用 List&lt;Test&gt; 类类型,但这似乎是不可能的(据我所知)......如果没有更好的解决方案,我会奖励给你积分。
  • 您确定 List 给您带来了问题吗?似乎您可以使用 DataContract 序列化 List。链接:msdn.microsoft.com/en-us/library/aa347850.aspx
  • 在迈克尔上面建议的解决方案中,List&lt;Test&gt; 而不是List&lt;Object&gt; 给我带来了问题。在我最初的测试中,不是列表给我带来了问题,而是从List&lt;Test&gt; 继承的自定义类TestCol 给我带来了问题。删除 TestCol 完全解决了我的问题,但这不是我想要的。上面的解决方案似乎还可以,但我希望我可以使用List&lt;Test&gt; 而不是List&lt;Object&gt;
【解决方案2】:

JavaScriptSerializer 序列化:IEnumerable -> JavaScript 数组

当使用 JavaScriptSerializer 时,它会自动将一个 IEnumerable(不带 IDictionary)类型(包括 List 或从它派生的任何内容)转换为数组。

反序列化:JavaScript 数组 -> IEnumerable -> 集合对象

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

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

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

未继承的构造函数

然而,TestCol 确实没有 有这样的构造函数!它使用List&lt;Test&gt; 而不是TestCol(派生自List&lt;Test&gt;)的原因是,唯一在类之间继承的是构造函数!

因此,JavaScriptSerializer 无法从 IEnumerable 构造 TestCol。所以它默默地失败了。

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

现在 JavaScriptSerializer 可能会尝试从这个 IEnumerable&lt;Test&gt; 创建一个 List&lt;Test&gt;,然后尝试将其转换为 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&lt;Test&gt;TestCol 的显式类型转换。

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

【讨论】:

    【解决方案3】:

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

      foreach (Test test in input.Components.TestCol)
    

    在下面重新评论,这样行吗?

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

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

    【讨论】:

    【解决方案4】:

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

    【讨论】:

    • 这不是问题所在。问题是我将 JSON 发布到 web 服务,它会自动将这些转换为自定义类。一切正常,除了TestCol 类。返回 String 仅用于测试目的。
    • 很糟糕。希望有人觉得我的回答有用,哈哈。
    【解决方案5】:

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

    您写道,public class TestCol : List&lt;Test&gt; { } 之类的类对 JavaScriptSerializer 不利,但具有 List&lt;Test&gt; 作为属性 (public class MainTest { public List&lt;Test&gt; 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

    【讨论】:

    • @jerone:你能重现我建议的更改吗?如果有帮助,我可以上传您项目的修改代码并将网址发布给您。
    • 这与我在第一个解决方案中提到的建议完全相同。问题是该项目大量使用TestCol 类来定义new List&lt;Test&gt;();,我无法替换它。
    【解决方案6】:

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

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

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

    JavaScriptSerializer 支持序列化 List 的。序列化 TestCol 属性应该不会有问题,因为 JavaScriptSerializer 自动支持将所有实现 IEnumerable(但不包括 IDictionary)的类型(包括 List)序列化为 JSON 数组。

    您的错误似乎是 JavaScriptSerializer 没有正确处理从 List(或从实现 IEnumerable 的类)继承的类。在您的第一个解决方法中,您消除了从 List 继承的类。在您的第二个解决方法中,您跳过了基类的所有功能,但在属性中重新实现了 List。

    您的 JSON 帖子数据当前如下所示:

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

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

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

    因为您实际上是在序列化 List 的“Items”属性。因此,您的 JSON 发布数据只是将 Test 对象提供给错误的位置,并且您的 TestCol Components 属性最终为空。

    我建议您添加一个 Web 服务方法来输出一个测试 MainTest 对象,看看它序列化成什么。您可能会发现它增加了一个级别。

    【讨论】:

    • 感谢您抽出宝贵时间。将您的建议付诸行动,并在其 Components 中创建了一个带有 2 个 TestMainTest。结果如预期的那样:({__type:"JSONtoClassWebservice.MainTest", Components:[{Name:"Test 1"}, {Name:"Test 2"}]})。反正我们可以骗过 JavaScriptSerializer?
    • 这是用TestCol 序列化的,对吧?在这种情况下,只需将此确切的 JSON(连同 __type 属性)发布回您的 Web 服务,看看会发生什么...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多