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:
Remove the
TestCol
class and change theComponents
property toList<Test>
datatype:[DataContract()] public class MainTest { public List<Test> Components { get; set; } } [DataContract()] public class Test { public Test() { } public String Name { get; set; } }
Or ad开发者_Go百科d 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
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
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); }
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...
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
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.
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.
精彩评论