So I have worked out how to pass my custom objects into ASP.Net json webservices. Works a charm.
Problem I am having is passing in straight arrays of my custom objects or alternatively passing in arrays that are parameters of my custom objects.
So for example...
Public Class WebService1
Inherits System.Web.Services.WebService
<WebMethod()> _
<ScriptMethod(ResponseFormat:=ResponseFormat.Json)> _
Public Function AddPersonList(ByVal PersonList As PersonList) As String
Debug.Assert(False)
End Function
Public Class Person
Public Sub New()
End Sub
Public Property FirstName As String
Public Property LastName As String
End Class
Public Class PersonList
Inherits List(Of Person)
End Class
End Class
<script>
$(function () {
$.ajax({
type: "POST",
url: "WebService1.asmx/AddPersonList",
data: " { PersonList: [ { FirstName: 'Max', LastName: 'Gershkovich' }, { FirstName: 'Test1', LastName: 'Test2' } ] }",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (e) { debugger; },
error: function (e) { debugger; }
});
});
</script>
Fails with error: The value \"System.Collections.Generic.Dictionary`2[System.String,System.Object]\" is not of type \"WebApplication1.WebService1+Person\" and cannot be used in this generic collection.\r\nParameter name: value
So how do I explain to ASP.net that it is infact an array of Person?
Please note: That changing the function to as List(of Person) or ArrayList does work but given that I implement my own custom collections this is not optimal for me.
UPDATE: Ok so what I have worked out so far is that this problem is definitely associated with how the JavascriptSerializer uses the SimpleTypeResolver to resolve types. Basically if I do something like this
Public Function AddPersonList(ByVal PersonList As String) As PersonList
I can recreate the error using the following code.
Dim PersonList As PersonList = jsonSerializer.Deserialize(Of PersonList)(PList)
However when I provide my own custom type resolver along the lines of
Dim jsonSerializer As New JavaScriptSerializer(New MyCustomTypeResolver)
I can successfully create an instance of my custom list.
Now I have worked out how to provide my own custom convertor in the web.config file. Along the lines of this….
<system.web.extensions>
<scripting>
<webServices>
<jsonSerialization>
<converters>
<add name="MyCustomConverter" type="WebApplication1.MyCustomConverter" />
</converters>
</jsonSerialization>
</webServices>
</scripting>
</system.web.extensions>
But the problem is that I can’t find a way to specify a CustomTypeResolver. My assumption is that this is the only piece of the puzzle I am missing.
PSS: I have tried to use the assembly fully qualified name as specified in the docs for the SimpleTypeResolver (SimpleTypeResolver MSDN) but this throws a "Operation is not valid due to the current state of the object." exception - which is an error caused when the TypeResolver cannot resolve the name (I know this by testing with my CustomTypeResolver)
UPDATE @ Oleg and Sean:
I sincerely appreciate your input and have infact changed my application to reflect your suggestions; having said that the problem with this change is that it requires reworking of my class implementation and its associated behaviour in ways that I would have preferred to avoid.
My problem was never passing in a List(of Object) into my webservice (I simply posed the question as such to simplify it for stackoverflow). In such a case I would be willing to completely accept using a Generic List(of) but my problem was actually that one of my custom objects implemented a property with a strong typed List(of) so for example:
Customer {CustomerID AS Guid, FirstName AS String, LastName AS String, EmailAddresses AS EmailAddressList}
which now needs to be changed to
Customer {CustomerID AS Guid, FirstName AS String, LastName AS String, EmailAddresses AS List(Of EmailAddress)}
This is admittedly not the end of the world and is probably better in the webservice context (as you have suggested) but definitely a detriment when it comes to internal application usage of my Collection properties. What this means is that once I have this property I either need to cast it to my CustomCollectionList every time I want to use some advanced feature or I need to implement another property which exposes the CustomCollectionList. Either solution leaves a nasty taste in my mouth.
First of all the data
{ PersonList: [ { FirstName: 'Max', LastName: 'Gershkovich' }, { FirstName: 'Test1', LastName: 'Test2' } ] }
which you send to the web service are wrong JSON data. You can verify the data on http://www.jsonlint.com/. Correct JSON data will be
{"PersonList":[{"FirstName":"Max","LastName":"Gershkovich"},{"FirstName":"Test1","LastName":"Test2"}]}
Moreover I recommend you never make JSON manually. Instead of that you should use JSON.stringify. In the most cases the function are even natively supported in the web browsers (sometime after updates like here). Correct serialization of Web service parameters can be
$.ajax({
data: {PersonList:JSON.stringify(t)}
// other parameters
});
(see here for more details) or
$.ajax({
data: JSON.stringify({ PersonList: t })
// other parameters
});
where
var t = [ { FirstName: 'Max', LastName: 'Gershkovich' },
{ FirstName: 'Test1', LastName: 'Test2' } ];
Which version of data representation JSON.stringify({ PersonList: t })
or {PersonList:JSON.stringify(t)}
is correct depend on other things. It is easy to test which from there work in your environment.
Next small problem: you should better use List(Of Person)
directly in the parameters of AddPersonList
instead of the usage of types PersonList
inherited from List(Of Person)
.
UPDATED: Just now I read your comments about List(Of Person)
or PersonList
to another answer. To prevent the same discussion I decide to write my opinion about this. It is not important which classes you use inside of your web service. You should design the interface of the web service so that it should be simple and clear for every person who knows nothing about your implementation. The input data of the method (at least what you included in the question) can be perfectly described with List(Of Person)
. Moreover List(Of Person)
it is good for the data transfer. Inside of the method implementation you can convert List(Of Person)
to any other classes which are good for the method internals. For example, it is very simple to construct PersonList
from the List(Of Person)
. So I recommend you to follow the way: keep interface of the web service free from the implementation details.
UPDATED 2: It is not a problem at all if the object, which is the input parameter of the web method, has as the property another objects like List(Of EmailAddress)
. For example, if you defines EmailAddress
from your last example as
Public Class EmailAddress
Public Property Type As String
Public Property Address As String
End Class
and Customer
like
Public Class Customer
'Public CustomerID As Guid
Public Property FirstName As String
Public Property LastName As String
Public Property EmailAddresses As List(Of EmailAddress)
End Class
then you will have no problem with AddPersonList
web method defined as following
<WebMethod()> _
Public Function AddPersonList(ByVal PersonList As List(Of Customer)) As String
In the same way you can use
<WebMethod()> _
Public Function AddPersonList1(ByVal input As InputData) As String
with
Public Class InputData
Public Property PersonList As List(Of Customer)
End Class
or many other versions of object to send the information to the web server using $.ajax
.
On the client side you can define myData
as following
var myData = [
{ FirstName: 'Max', LastName: 'Gershkovich',
EmailAddresses: [
{ Type: 'SMTP', Address: '[email protected]' },
{ Type: 'SMTP', Address: '[email protected]' },
]
},
{ FirstName: 'Test1', LastName: 'Test2' }
];
and then send the data to the web server with
$.ajax({
type: "POST",
url: "WebService1.asmx/AddPersonList",
data: JSON.stringify({ PersonList: myData }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
alert(data.d);
},
error: function (xhr, textStatus, errorThrown) {
alert("Error Occured!" + " | " + xhr.responseText + " | " +
textStatus + " | " + errorThrown);
}
});
or
$.ajax({
type: "POST",
url: "WebService1.asmx/AddPersonList1",
data: JSON.stringify({ input: {PersonList: myData} }),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (data) {
alert(data.d);
},
error: function (xhr, textStatus, errorThrown) {
alert("Error Occured!" + " | " + xhr.responseText + " | " +
textStatus + " | " + errorThrown);
}
});
All the above work without any problem. You can download the test example, which do this (Default.htm should be started. To call the method you should click on the first or the second button).
If you look at the myData
object defined in the client code and then look at the usage of JSON.stringify
above you will see that you will have no problem in sending any complex objects including arrays and properties. On the server side you can use List(Of Customer)
, List(Of EmailAddress)
or list of other objects as the representations of JavaScript Arrays. All will work. Only your original example with the object inheritance is a bad one.
If you will try to design the interface of the web service looking from the client side and not from the internal server structures you will easy construct the classes for the corresponding input parameters of the web methods. And all will work immediately. If you will need to initialize your internal classes with the information you will be able to do it very simply. Such kind of data conversion will be very easy and you will not need to write any CustomTypeResolver which do in fact the same.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With