JSON не работает с типизированным классом в Webservice - PullRequest
1 голос
/ 10 марта 2011

У меня есть класс, подобный следующему:

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]
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 и измените веб-метод:

    [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

Ответы [ 6 ]

1 голос
/ 20 марта 2011

Таким образом, это решение меняет список на объект вместо теста.Я надеялся изменить как можно меньше кода (мне не нравится делать приведения в циклах 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 Отображение объекта в словарь и наоборот

0 голосов
/ 24 марта 2011

Сериализация JavaScriptSerializer: IEnumerable -> Массив JavaScript

Когда используется JavaScriptSerializer, он автоматически преобразует тип IEnumerable (без IDictionary) - который охватывает List <> или что-либо производное от него - в массив.

Десериализация: массив JavaScript -> IEnumerable -> объект коллекции

Теперь, после десериализации из JSON, JavaScriptSerializer должен взять массив, создать IEnumerable, а затем создать объект для поля, передав этот IEnumerable в его конструктор.

Создание объекта Collection с помощью конструктора

Теперь для List <> у вас есть перегрузка конструктора, которая принимает IEnumerable. Поэтому, если вы указали List<Test> в качестве типа вашего компонента, он прекрасно его создаст.

Конструкторы не наследуются

Однако, TestCol НЕ имеет такой конструктор! Причина, по которой он работал с List<Test>, а не с TestCol (который происходит от List<Test>), заключается в том, что единственное, что не унаследовано между классами, это конструкторы!

Следовательно, JavaScriptSerializer не имеет никакого способа создать TestCol из IEnumerable. Так что это молча терпит неудачу.

Десериализовать массив, создав список, затем приведя к типу

Теперь 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); }
0 голосов
/ 23 марта 2011

Вы, похоже, используете ASMX (не WCF), потому что вы пропустили атрибуты [DataMember] во всех ваших общедоступных свойствах и все еще сериализуетесь. WCF "opt-in", поэтому вы не должны видеть какой-либо сериализации должным образом.

В результате все атрибуты [DataContract] бесполезны.

ASMX по умолчанию использует JavaScriptSerializer, если вы используете ScriptManger и выводите JSON. JavaScriptSerializer отключен (это означает, что все открытые свойства автоматически сериализуются, если они не помечены [ScriptIgnoreAttribute]).

JavaScriptSerializer поддерживает сериализацию List <>. У вас не должно возникнуть проблем с сериализацией вашего свойства TestCol, потому что JavaScriptSerializer автоматически поддерживает сериализацию всех типов, которые реализуют IEnumerable (но не IDictionary) - который включает List <> - в массивы JSON.

Кажется, ваша ошибка в том, что JavaScriptSerializer неправильно обрабатывает классы, которые наследуют из List <> (или от класса, реализующего IEnumerable). В своем первом обходном пути вы исключили класс, унаследованный от List <>. Во второй работе вы пропустили все функции базового класса, но повторно реализовали List <> в свойстве.

Ваши данные в формате JSON в настоящее время выглядят так:

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

Однако у вас есть один дополнительный уровень или перенаправление в сериализаторе (наследование от List<Test> -> TestCol). Возможно, что сериализатор ищет:

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

потому что вы по существу сериализуете свойство "Items" List <>. Итак, ваши данные постов JSON просто передают объекты Test в неправильное место, а свойство TestCol Components оказывается пустым.

Я бы предложил добавить метод веб-службы для вывода тестового объекта MainTest, чтобы увидеть, во что он сериализуется. Вы, вероятно, обнаружите, что это добавляет дополнительный уровень.

0 голосов
/ 16 марта 2011

Если вы используете службы 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);
    }
});

Как вы видите все будет очень просто и это работа.Для получения более подробной информации о том, как вы можете отправлять сложные данные, я рекомендую вам прочитать другой ответ и this .

0 голосов
/ 12 марта 2011

Если вы ожидаете JSON, вам нужно вернуть JSON. Проверьте с помощью System.Web.Script.Serialization.JavaScriptSerializer http://msdn.microsoft.com/en-us/library/system.web.script.serialization.javascriptserializer.aspx

0 голосов
/ 10 марта 2011

хммм, это не сработало в веб-методе?

  foreach (Test test in input.Components.TestCol)

Re комментарий ниже, это работает тогда?

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

Это должно работать, потому что класс можно перечислить ...

...