JSON 不适用于 Web 服务中的类型化类

发布于 2024-10-21 16:57:38 字数 2405 浏览 3 评论 0原文

我有一个如下所示的类:

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< /code> 数据类型:

    <前><代码>[DataContract()] 公共类MainTest { 公共列表<测试>组件{获取;放; } } [数据契约()] 公开课测试 { 公共测试(){} 公共字符串名称{获取;放; } }
  2. 或添加TestCol 类的额外属性并更改 webmethod:

    <前><代码>[DataContract()] 公共类 TestCol : List; { 公共列表<测试>组件{获取;放; } } [数据契约()] 公共类MainTest { 公共 TestCol 组件 { 获取;放; } } [数据契约()] 公开课测试 { 公共测试(){} 公共字符串名称{获取;放; } }

    &

    <前><代码>[WebMethod] 公共字符串测试(MainTest 输入) { 字符串 rtrn = String.Empty; foreach(在input.Components.Components中测试测试) rtrn += 测试.名称; 返回rtrn; }

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

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

I'm having a class like the following:

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

And a webservice with the following webmethod like this:

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

Which is called by AJAX with the following method:

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

When executing the AJAX call, it will return errors. I found out that the error is with the typed class TestCol, which has no properties.
Now do I have found 2 solutions that require changes in the C# classes:

  1. Remove the TestCol class and change the Components property to List<Test> datatype:

    [DataContract()]
    public class MainTest
    {
        public List<Test> Components { get; set; }
    }
    
    [DataContract()]
    public class Test
    {
        public Test() { }
        public String Name { get; set; }
    }
    
  2. Or add an extra property to the TestCol class and change the webmethod:

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

Both solutions require changes in the C# classes, which I prefer not to, as other code is depended on it. Does anyone know a solution for this problem?

Edit: I've uploaded a test solution, containing above code: http://jeroenvanwarmerdam.nl/content/temp/JSONtoClassWebservice.zip

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

疑心病 2024-10-28 16:57:39

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

您编写了诸如 public class TestCol : List这样的类; { }不利JavaScriptSerializer,但是以 List 作为属性的类(public class MainTest { public ListComponents { 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);
    }
});

您如何看到所有内容都将非常简单并且有效。有关如何发送复杂数据的更多详细信息,我建议您阅读 另一个答案这个

If you use ASMX services the JavaScriptSerializer will be responsible for the data conversion and not the DataContractJsonSerializer. So all DataContract attributes which you use will not work.

You are write that classes like public class TestCol : List<Test> { } are bad for the JavaScriptSerializer, but classes having List<Test> as the property (public class MainTest { public List<Test> Components { get; set; }}) have no problem.

So I suggest to simplify you code to the following. The classes used as the parameters can be defines as

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

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

The WebMethod Test will be

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

and the ajax call can be

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

How you can see all will be very simple and it's work. For more details how you can send complex data I recommend you to read another answer and this.

A君 2024-10-28 16:57:39

您似乎正在使用 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 -> TestCol)。序列化程序可能正在寻找:

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

因为您本质上是序列化 List<> 的“Items”属性。因此,您的 JSON post 数据只是将 Test 对象提供到错误的位置,并且您的 TestCol Components 属性最终为空。

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

You seem to be using ASMX (not WCF) because you have omitted [DataMember] attributes on all your public properties and still get serialized. WCF is "opt-in", so you shouldn't be seeing any serialization of any properly.

As a result, all [DataContract] attributes are useless.

ASMX defaults to the JavaScriptSerializer if you are using ScriptManger and outputing JSON. The JavaScriptSerializer is "opt-out" (which means that all public properties are automatically serialized unless marked with [ScriptIgnoreAttribute]).

The JavaScriptSerializer supports serializing List<>'s. You should not be having problems serializing your TestCol property because JavaScriptSerializer automatically supports serializing all types that implement IEnumerable (but not IDictionary) -- which includes List<> -- into JSON arrays.

Your error seems to be that the JavaScriptSerializer does not properly handle classes that inherit from List<> (or from a class implementing IEnumerable). In your first work-around, you eliminated the class that inherited from List<>. In your second work-around, you skipped all functionalities of the base class, but re-implemented the List<> in a property.

Your JSON post data currently looks like:

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

However, you have one extra level or redirection in the serializer (inheriting from List<Test> -> TestCol). It is possible that the serializer is looking for:

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

because you are essentially serializing the "Items" property of List<>. So, your JSON post data is just feeding Test objects to the wrong place, and your TestCol Components property ends up empty.

I'd suggest that you add a web service method to output a test MainTest object to see what it serializes into. You'll probably find that it puts in an additional level.

半葬歌 2024-10-28 16:57:38

所以这个解决方案将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 提供
将对象映射到字典,反之亦然

So this solution changes the List to Object instead of Test. I hoped to change as little code as possible (i dislike having to do casts in foreach loops). The below code does so with two function additions and the previously mentioned inheritance change.

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

Convert function courtesy TurBas
Mapping object to dictionary and vice versa

走野 2024-10-28 16:57:38

JavaScriptSerializer序列化:IEnumerable -> JavaScript Array

当使用 JavaScriptSerializer 时,它会自动转换 IEnumerable(不带 IDictionary)类型——涵盖 List<> 类型。或从它派生的任何东西——放入数组中。

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

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

现在通过构造函数构造 Collection 对象

,对于 List<>您有一个采用 IEnumerable 的构造函数重载。因此,如果您将 List 作为组件的类型,它会创建得很好。

构造函数不是继承的

但是,TestCol 确实没有有这样的构造函数!它与 List 配合使用而不是与 TestCol(派生自 List)配合使用的原因是,唯一类之间继承的是构造函数!

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

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

现在 JavaScriptSerializer 可能会尝试从此 IEnumerable 创建一个 List,然后尝试转换它进入 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 的构造函数。

如果仍然不起作用,请实现从 ListTestCol 的显式类型转换。

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

JavaScriptSerializer serialization: IEnumerable -> JavaScript Array

When the JavaScriptSerializer is used, it automatically converts an IEnumerable (without IDictionary) type -- that covers List<> or anything derived from it -- into an array.

Deserialization: JavaScript Array -> IEnumerable -> Collection Object

Now, upon deserialization from JSON, the JavaScriptSerializer must take the array, create an IEnumerable, then create an object for the field by passing that IEnumerable into its constructor.

Constructing Collection object via Constructor

Now, for List<> you have a constructor overload that takes an IEnumerable. So if you put List<Test> as the type of your component it creates it fine.

Constructors not inherited

However, TestCol does NOT have such a constructor! The reason why it worked with List<Test> and not with TestCol (which derives from List<Test>) is that the only thing that is not inherited between classes are constructors!

Therefore, the JavaScriptSerializer does not have any way to construct a TestCol from an IEnumerable. So it fails silently.

Deserialize Array by Creating List, then Casting to Type

Now the JavaScriptSerializer may then attempt to create a List<Test> from this IEnumerable<Test>, and then try to cast it into a TestCol.

Possible Solution

Solution: Try putting in:

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

as your TestCol's constructors.

And if it still doesn't work, implement an explicit type cast from List<Test> to TestCol.

public static explicit operator TestCol(IList<Test> list) { return new TestCol(list); }
会发光的星星闪亮亮i 2024-10-28 16:57:38

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

  foreach (Test test in input.Components.TestCol)

下面再评论一下,这行得通吗?

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

它应该有效,因为可以枚举一个类......

hmmm this didn't work in the web method?

  foreach (Test test in input.Components.TestCol)

Re comment below, does this work then?

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

It should work because a class can be enumerated...

林空鹿饮溪 2024-10-28 16:57:38

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

If you're expecting JSON, you'll need to return JSON.
Check with the System.Web.Script.Serialization.JavaScriptSerializer
http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文