I will start with my problem:
I have a web method that I am calling through AJAX / JSON. Let me call it "GlobalMethod", which is used to manage the "container" object, which has a list of elements derived from the same "base class". This is an example of my situation code:
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] [System.ComponentModel.ToolboxItem(false)] [System.Web.Script.Services.ScriptService] public class MyService : System.Web.Services.WebService { [WebMethod] public string GlobalMethod(Container data) { string result = ""; foreach (var item in data.Items) { result += item.BaseProperty; if (item is FirstDerivedClass) result += ((FirstDerivedClass)item).FirstDerivedProperty; else if (item is SecondDerivedClass) result += ((SecondDerivedClass)item).SecondDerivedProperty; } return result; } } public class Container { public List<BaseClass> Items; } public abstract class BaseClass { public string BaseProperty; } public class FirstDerivedClass : BaseClass { public string FirstDerivedProperty; } public class SecondDerivedClass : BaseClass { public string SecondDerivedProperty; }
This method just doesn't work. I will not be able to call this method using the JavascriptSerializer by default, because the serializer cannot decide which objects the container will have: they can be of type FirstDerivedClass or SecondDerivedClass.
So, while browsing the web to solve my problem, I came across Json.NET , the JsonConvert.DeserializeObject method is able to get the original type of an object that was serialized using JsonConvert.SerializeObject, because it adds a property called "$ type".
Now my problem is: how can I use the web method with this serializer instead of the standard one used by ASMX? If the method signature remains
[WebMethod] public string GlobalMethod(Container data)
then I will get a completely empty container object, because the environment is doing a deserialization job and does not know which elements to create, and I canβt say that the structure must use Json.NET for a job that can completely deserialize the data.
If I try to change the method this way
[WebMethod] public string GlobalMethod(string data) { string result = ""; Container container = JsonConvert.DeserializeObject<Container>(data, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }); foreach (var item in container.Items) { ...
then I will get another 500 server error, because "without a constructor without parameters for type u0027system.string u0027"
Well, I hope my problem is clear ... I spent all day on this, and I did not seem to find a solution. Revising the architecture of my method to avoid using derived classes is not really an option (beware that the real code is much more complicated, I just simplified it to get to the point!).
By the way, I will add that the ajax method calling the web method is executed as follows:
$.ajax({ type: "POST", url: wsUrl + method, data: JSON.stringify(data), contentType: "application/json; charset=utf-8", dataType: "json", processData: false, success: function(msg) { try{ success(JSON.parse(msg.d)); } finally { } }, error: error });
Thanks to everyone who shares their knowledge with me!
I would like to share how I really solved my problem. As I said, I modified my method this way:
[WebMethod] public string GlobalMethod(string data) { string result = ""; Container container = JsonConvert.DeserializeObject<Container>(data, new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects }); foreach (var item in container.Items) {
Initially, I started getting error 500 "without constructor without parameters defined for type u0027system.string u0027"
But in the end, I found out the reason: the json string that was moving from the client to the server was deserialized by the .NET platform in the contained object, which was not a string, but the actual JSON object!
So, in my ajax method, the trick was as follows:
$.ajax({ type: "POST", url: wsUrl + method, **data: JSON.stringify(JSON.stringify(data)),** contentType: "application/json; charset=utf-8", dataType: "json", processData: false, success: function(msg) { try{ success(JSON.parse(msg.d)); } finally { } }, error: error });
which means that the json object is encapsulated inside another line, which will be deserialized manually on my server side of GlobalMethod (line) via Json.NET.
Obviously, I will not include this double "stringify" in the ajax routine, but I will pay attention to passing JSON.stringified data into the input for the ajax procedure itself!