I am having trouble sending JSON to a WebMethod. Here is the way that I am trying to do it. If there is a better way to do this please let me know. What I am trying to do is save the JSON object off in a database.
JavaScript
function TEST() {
var str = '[{
"Key": 6311,
"Start": "123 Start",
"End": "456 End ",
"Date": "2/2/2012",
"Order": null,
"EstMiles": 0,
"Stops": [
{"StopAddy": "123 Stop Addy "},
{"StopAddy": "456 Stop Addy"},
{"StopAddy": "789 Stop Addy"}
]
}]'; // Whitespace added for clarity
$.ajax({
type: "POST",
url: "WebService1.asmx/Test",
data: str,
//contentType: "plain/text",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(msg) {
alert(msg.d);
},
error: function(e) {
alert(e.responseText);
}
});
}
VB.NET
Public Function Test(ByVal o As Object()) As String
'do your processing
Return "success"
End Function
Public Class [Stop]
Public Property StopAddy() As String
Get
Return m_StopAddy
End Get
Set(ByVal value As String)
m_StopAddy = value
End Set
End Property
Private m_StopAddy As String
End Class
Public Class RootObject
Public Property Key() As Integer
Get
Return m_Key
End Get
Set(ByVal value As Integer)
m_Key = value
End Set
End Property
Private m_Key As Integer
Public Property Start() As String
Get
Return m_Start
End Get
Set(ByVal value As String)
m_Start = value
End Set
End Property
Private m_Start As String
Public Property [End]() As String
Get
Return m_End
End Get
Set(ByVal value As String)
m_End = value
End Set
End Property
Private m_End As String
Public Property [Date]() As String
Get
Return m_Date
End Get
Set(ByVal value As String)
m_Date = value
End Set
End Property
Private m_Date As String
Public Property Order() As Object
Get
Return m_Order
End Get
Set(ByVal value As Object)
m_Order = value
End Set
End Property
Private m_Order As Object
Public Property EstMiles() As Integer
Get
Return m_EstMiles
End Get
Set(ByVal value As Integer)
m_EstMiles = value
End Set
End Property
Private m_EstMiles As Integer
Public Property Stops() As List(Of [Stop])
Get
Return m_Stops
End Get
Set(ByVal value As List(Of [Stop]))
m_Stops = value
End Set
End Property
Private m_Stops As List(Of [Stop])
End Class
The error I am getting is
{"Message":"Type \u0027System.Collections.Generic.IDictionary`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Object, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]\u0027 is not supported for deserialization of an array.","StackTrace":"
at System.Web.Script.Serialization.ObjectConverter.ConvertListToObject(IList list, Type type, JavaScriptSerializer serializer, Boolean throwOnError, IList& convertedList)
at System.Web.Script.Serialization.ObjectConverter.ConvertObjectToTypeInternal(Object o, Type type, JavaScriptSerializer serializer, Boolean throwOnError, Object& convertedObject)\r\n
at System.Web.Script.Serialization.ObjectConverter.ConvertObjectToTypeMain(Object o, Type type, JavaScriptSerializer serializer, Boolean throwOnError, Object& convertedObject)\r\n
at System.Web.Script.Serialization.ObjectConverter.ConvertObjectToType(Object o, Type type, JavaScriptSerializer serializer)\r\n
at System.Web.Script.Serialization.JavaScriptSerializer.Deserialize(JavaScriptSerializer serializer, String input, Type type, Int32 depthLimit)\r\n
at System.Web.Script.Serialization.JavaScriptSerializer.Deserialize[T](String input)\r\n
at System.Web.Script.Services.RestHandler.GetRawParamsFromPostRequest(HttpContext context, JavaScriptSerializer serializer)\r\n
at System.Web.Script.Services.RestHandler.GetRawParams(WebServiceMethodData methodData, HttpContext context)\r\n
at System.Web.Script.Services.RestHandler.ExecuteWebServiceCall(HttpContext context, WebServiceMethodData methodData)",
"ExceptionType":"System.InvalidOperationException"}
Based on your stack trace, I assume you are using Asp.net on the server side?
If so, I believe Asp.net requires you to have a main object that wraps list of objects for json data. So, please change your input to the following, and change your server code a bit to handle the extra wrapping layer:
var str = '{
"data":
[
{
"Key": 6311,
"Start": "123 Start",
"End": "456 End ",
"Date": "2/2/2012",
"Order": null,
"EstMiles": 0,
"Stops": [
{
"StopAddy": "123 Stop Addy "
},
{
"StopAddy": "456 Stop Addy"
},
{
"StopAddy": "789 Stop Addy"
}
]
}
]
}'
Related
I have this Object
{
"Data1": 1,
"Data2": "some string",
"Data3": [
{
"ID": 0,
"Name": "some name",
"SomeArray": []
},
{
"ID": 0,
"Name": "another name",
"SomeArray": [
"DataA": 0,
"DataB": "some data again"
]
}],
"Data4": "some string again"
}
The model which it will receive in the controller is this:
public class Data
{
public int Data1 { get; set; }
public string Data2 { get; set; }
public List<AnotherClass> Data3 { get; set; }
public string Data4 { get; set; }
}
public class AnotherClass
{
public int ID { get; set; }
public string Name { get; set; }
public List<DataList> SomeArray { get; set; }
}
public class DataList
{
public int DataA { get; set; }
public string DataB { get; set; }
}
The data that is to be set in Data3 is coming from a table where new inputs are retrieved from a modal popup and populated in the table. SomeArray data is coming from a dropdown which uses Select2 tool to set multiple choices in a dropdown.
The idea is that once the user clicks "Save", it will get all of the data, including those in the table and form this JSON object. From this object, I want to convert it into a FormData. However when it iterates into Data3, it doesn't convert it into an array of objects even if I stringify it. I've also tried this one Convert JS Object to form data but was unsuccessful.
Here's what I did so far:
var details = GetDetails(); // This contains `Data1`, `Data2` and `Data4`
var data3 = GetData3FromTable(); // This contains the list of `Data3`
var result = new FormData();
for (var key in details) {
result.append(key, details[key]);
}
result.append("Data3", JSON.stringify(data3));
result.append("UploadFile", $("#upload")[0].files[0]);
// do ajax put or post after this line
Is there any jquery or javascript plugin that I can use to convert this object into FormData? Or is there any other way to convert it into FormData?
You can send deeply nested data in HTML forms, but I have noticed that different web servers may interpret the more complex structures differently, so you may need to check your framework/app's middleware to ensure proper decoding. You may find it easier to read values into Javascript and send the data from there.
Your Data3 object could be represented using something like this:
<input type="text" name="data[Data3][0][id]" value="0">
<input type="text" name="data[Data3][0][Name]" value="some name">
<input type="text" name="data[Data3][0][SomeArray][]" value="">
You need a "parent" field (data in this example) which houses the child elements.
Note that you have to be careful of any [] -- you may need to specify the array index manually as above, otherwise the "grouping" of values won't be respected.
Also beware of names ending with []: those fields will still send values, even if it's an empty string, so you may need to filter those out.
I found this link object-to-form-data where it converts any object, including nested arrays and upload files into FormData. However, there's just one change that needs to be done:
From the link, replace objectToFormData(obj[property], fd, property); with objectToFormData(obj[property], fd, formKey);. This way, it sets the original property as the key. The output of the FormData when it is passed to the controller will be this:
Data1: 1
Data2: some string
Data3[0][ID]: 0
Data3[0][Name]: some name
Data3[0][SomeArray]: []
Data3[1][ID]: 0
Data3[1][Name]: another name
Data3[1][SomeArray][0][DataA]: 0
Data3[1][SomeArray][0][DataB]: some data again
Data4: some string again
I hope this helps others!
I try to send a JSON object from JavaScript to Controller. I get 400 status code when sending the object. I tried many methods, but my last approach is below. Do I miss something? Why do I keep getting 400?
ajax post call:
$.ajax({
type: "POST",
url: "http://localhost:8080/services/saveReservation",
cache: false,
data: saveReservation,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
console.debug("After success");
}
});
my object looks like this:
{
"accommodaion":"a0d8185c-a238-5qe7-aa48-5c196b108aba",
"totalOutputPrice":90,
"bookerInfo":{
"country":"xa",
"homeAddress":"",
"phoneNumber":"0019382663773",
"contactChoice":"phone",
"name":"test name",
"id":"87"
},
"creditCard":{
"holderName":"holder test",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
},
"checkIn":"2016-11-16 06:43:19.77",
"checkOut":"2017-03-16 06:43:19.77",
"totalTax":4,
"totalVat":13,
"roomOutputPrice":"77"
}
Controller:
#RequestMapping(value = "/saveReservation", method = RequestMethod.POST)
public test saveReservation(
#RequestBody test saveReservation) {
System.out.println(saveReservation.getAccommodaion());
System.out.println(saveReservation.getBookerInfo().getName());
System.out.println(saveReservation);
return saveReservation;
}
Classes:
private class bookerInfo {
private String country;
private String homeAddress;
private String phoneNumber;
private String contactChoice;
private String name;
private String id;
//getters setters
}
private class creditCard {
private String holderName;
private String cardType;
private String expMonth;
private String expYear;
private String cvc;
//getters setters
}
private class test {
private String accommodaion;
private Float totalOutputPrice;
private bookerInfo bookerInfo;
private creditCard creditCard;
private String checkIn;
private String checkOut;
private Float totalTax;
private Float totalVat;
private Float roomOutputPrice;
//getters setters
}
Error in framework:
16:12:14,096 DEBUG ExceptionHandlerExceptionResolver:134 - Resolving exception from handler [public ba.projectService.controller.test ba.projectService.controller.G2BController.saveReservation(ba.projectService.controller.test)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"])
16:12:14,099 DEBUG ResponseStatusExceptionResolver:134 - Resolving exception from handler [public ba.projectService.controller.test ba.projectService.controller.G2BController.saveReservation(ba.projectService.controller.test)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"])
16:12:14,099 DEBUG DefaultHandlerExceptionResolver:134 - Resolving exception from handler [public ba.projectService.controller.test ba.projectService.controller.G2BController.saveReservation(ba.projectService.controller.test)]: org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"]); nested exception is com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "cardNumber" (class ba.projectService.controller.creditCard), not marked as ignorable (5 known properties: , "cvc", "expMonth", "holderName", "cardType", "expYear"])
at [Source: org.apache.catalina.connector.CoyoteInputStream#793ea01d; line: 1, column: 287] (through reference chain: ba.projectService.controller.test["creditCard"]->ba.projectService.controller.creditCard["cardNumber"])
You need to serialize your javascript object with JSON.stringify.
Try:
$.ajax({
type: "POST",
url: "http://localhost:8080/services/saveReservation",
cache: false,
data: JSON.stringify(saveReservation),
contentType: "application/json; charset=utf-8",
dataType: "json",
processData: false,
success: function(data) {
console.debug("After success");
}
});
And add the content type to the request mappging:
#RequestMapping(value = "/saveReservation", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
Make sure you have defined the json message converters. If you have Jackson as dependecy Spring do it for you.
Your new message is clear. You are sending a property from javascript that does not exist in your class. I am not sure whether you made a mistake or it is what you want. The property "cardNumber" does not exist at your CreditCard bean.
You can configure Jackson to ignore the unknown properties without throwing an exception using the annotation #JsonIgnoreProperties at the top of your class.
#JsonIgnoreProperties(ignoreUnknown = true)
private class creditCard {
private String holderName;
private String cardType;
private String expMonth;
private String expYear;
private String cvc;
//getters setters
}
If you want to send more than one credit card you could use a list. So, just changing your test bean to be able to work with a list of cards
private class test {
...
private List<creditCard> creditCards;
...
//getters setters
}
and at your javascript object, it should be now an array
{
... here all your properties
"creditCards":[{
"holderName":"card 1",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
},{
"holderName":"card 2",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
},{
"holderName":"card 3",
"cardType":"discover",
"cardNumber":"6011303031648258",
"expMonth":"01",
"expYear":"2017",
"cvc":"123"
}],
... more properties
}
should be enough. I hope you get the idea.
I get from a RESTful Service the following data:
[
{
"id": 42,
"type": 0,
"name": "Piety was here",
"description": "Bacon is tasty, tofu not, ain't nobody like me, cause i'm hot...",
}...
And I'm mapping with this class:
export enum Type {
Info,
Warning,
Error,
Fatal,
}
export class Message{
public id: number;
public type: Type:
public name: string;
public description: string;
}
But when I access 'type' in Angular2 I get only a int value. But I'd like to get a string value.
e.g:
'message.type=0'
{{message.type}} => should be Info
'message.type=1'
{{message.type}} => should be Warning
Enums in TypeScript are numbers at runtime, so message.type will be 0, 1, 2 or 3.
To get the string value, you need to pass that number into the enum as an index:
Type[0] // "Info"
So, in your example, you'll need to do this:
Type[message.type] // "Info" when message.type is 0
Docs
Enums in TypeScript are objects at runtime that have properties that go from int -> string and from string -> int for all possible values.
To access the string value you will need to call:
Type[0] // "Info"
Make sure that you are passing the correct type into the property accessor though because chained calls can result in the following:
Type[Type.Info] // "Info"
Type[Type[Type.Info]] // 0
Type["Info"] // 0
Type[0] // "Info"
I think with
{{message.type}}
you just get the mapped value and not the enum.
Please try following code.
{{TYPE[message.type]}}
This is how enum works in the typescript:
enum PrintMedia {
Newspaper = 1,
Newsletter,
Magazine,
Book
}
PrintMedia.Magazine; // returns 3
PrintMedia["Magazine"]; // returns 3
PrintMedia[3]; // returns Magazine
PrintMedia[PrintMedia.Magazine];// returns Magazine
I'm trying to post a object that includes an id (int) and a array, but I get a http 400 Bad request response from the server side. This is what I have so far...
Java Bean Object for Request:
public class GuardarVentaRequest {
private Integer idCliente;
private List<Venta> venta; ... (Getters and Setters code)
Java Object:
public class Venta {
private Integer id;
private String nombre;
private Integer precio;
private Integer cantidad;
private Integer total; ... (Getters and Setters code)
Java Controller:
#RequestMapping(value = "/guardarVenta", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody void venta(#RequestBody GuardarVentaRequest factura){
System.out.println(factura);
}
AngularJS Service:
function guardarVenta(array){
let factura = {
idCliente : parseInt($('#cliente').val()),
venta : array,
};
console.log(factura);
$http({
method: 'POST',
url: '/blue/guardarVenta',
contentType: 'application/json',
data: factura
}).then(
function successCallback(response){
console.log(response.statusText);
},
function errorCallback(response){
console.log(response.statusText);
}
);
}
Array:
$scope.productos = new Array();
let productoInfo = {
id: $scope.producto.id,
nombre: $scope.producto.nombre,
precio: $scope.producto.precio,
cantidad: $scope.cantidadProducto,
total: $scope.producto.precio * $scope.cantidadProducto
}
$scope.productos.push(productoInfo);
Output:
ADVERTENCIA: Failed to read HTTP message:
org.springframework.http.converter.HttpMessageNotReadableException: Could
not read document: Can not construct instance of com.blue.beans.Venta: no
suitable constructor found, can not deserialize from Object value (missing
default constructor or creator, or perhaps need to add/enable type
information?)
at [Source: java.io.PushbackInputStream#663359f3; line: 1, column: 28]
(through reference chain: com.blue.beans.GuardarVentaRequest["venta"]-
>java.util.ArrayList[0]); nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Can not construct
instance of com.blue.beans.Venta: no suitable constructor found, can not
deserialize from Object value (missing default constructor or creator, or
perhaps need to add/enable type information?)
at [Source: java.io.PushbackInputStream#663359f3; line: 1, column: 28]
(through reference chain: com.blue.beans.GuardarVentaRequest["venta"]-
>java.util.ArrayList[0])
Chrome's Network Tab Output
Any ideas?
Try these 3 things.
Add the default constructor to GuardarVentaRequest and Venta.
GuardarVentaRequest(){} &
Venta(){}
Check if a HTTPMessageConverter has been added to your spring config. Eg: MappingJackson2MessageConverter (Check for compatibility with your spring version).
Try serializing the request payload by using angular.toJson
Hope that helps!
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 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; }
}
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
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.