Reputation: 105
I'll start from my problem:
I have a webmethod that I'm calling via AJAX/JSON. Let's call it "GlobalMethod", that's used to manage a "container" object, that has a list of items derived from the same "baseclass". This is a code sample of my situation:
[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 simply doesn't work out. I won't be able to call this method using the default JavascriptSerializer, as the serializer isn't able to resolve what kind of objects the container will have: they could be of type FirstDerivedClass or SecondDerivedClass.
So, browsing the web for solutions to my problem, I've come across Json.NET, whose method JsonConvert.DeserializeObject is able to retrieve the original type of object that was serialized using the JsonConvert.SerializeObject, since it adds a property called "$type".
My problem now is: how can I make the webmethod using this serializer instead of the default one used by the ASMX? If the method signature remains
[WebMethod]
public string GlobalMethod(Container data)
then I'll get a totally empty Container object, as the framework is doing the deserialization job and doesn't know which items to instantiate, and I have no way of telling the framework that it should use Json.NET for the job, which would be able to fully deserialize the data.
If I try modifying 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'll get another server error 500 because "no parameterless constructor defined for type of u0027system.string u0027"
Ok, hopefully my problem is clear... I've been spending the entire day on it and I don't seem to find a solution. Revisiting the architecture of my method in order to avoid usage of derived classes isn't quite an option (beware the actual code is much more complex, I've just simplified it to get to the point!).
By the way, I'll add that the ajax method calling the webmethod is done this way:
$.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 anybody who will share his knowledge with me!
I'd like to share how I actually solved my problem. As I was already saying, 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 receiving the error 500 "no parameterless constructor defined for type of u0027system.string u0027"
But eventually I found out the reason: the json string that was travelling from client to server was being deserialized by the .NET framework into the contained object, which wasn't a string, but an actual JSON object!
So the trick was the following, in my ajax method:
$.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 string, which will be manually deserialized by my server-side GlobalMethod(string), through Json.NET.
Obviously I won't be including this double "stringify" in the ajax routine, but I'll pay attention to pass a JSON.stringified data in input to the ajax routine itself!
Upvotes: 4
Views: 3384
Reputation: 3232
This is just an hack, it's just an idea, doesn't sound elegant but it should work I'm just curious
change the container in this way:
public class Container
{
public List<MyUnionBaseClass> Items;
}
and define (pseudocode):
public class MyUnionBaseClass{
public void MyUnionBaseClass(BaseClass b){
this.setValue(b);
};
final public BaseClass getValue(){
if(first!=null) return (BaseClass) first;
if(second!=null)return (BaseClass) second;
return null;
}
final public setValue(BaseClass b){
if(b instanceOf firstClass){
first = (FirstClass) b;
}
if(b instanceOf secondClass){
second = (SecondClass) b;
}
}
private FirstDerivedClass first=null;
private SecondDerivedClass second=null;
}
P.s: this is very rought and should be improved. Does this make sense?
Upvotes: 3
Reputation: 7861
Change the webmethod to accept an object
[WebMethod]
public string GlobalMethod(object data)
This should resolve the issue you are having.
Upvotes: 2