4

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!

4
  • Have you considered using WCF? You have a lot more control over exactly how messages get serialized and deserialized. Commented Dec 11, 2012 at 21:34
  • This has been considered, but avoided for the time being, for consistency reasons with the rest of our architecture. We'd only do that if it were the only available solution. Thanks anyway, do you think that with WCF we'd be able to deserialize our objects? Any peculiar configuration? Commented Dec 12, 2012 at 7:40
  • 1
    I use the json.net serializer in WCF on my site, I followed this article to get me started. I also wrote this post on my blog with some of my learnings. To my knowledge, there's no way to override JSON formatting with ASP.NET web services, short of re-writing the entire handler yourself. Commented Dec 12, 2012 at 16:14
  • thanks Mike, very useful, although not really a solution to my problem. But definitely next time we'll be heading onto WCF, as they're far more customizable Commented Dec 13, 2012 at 7:51

2 Answers 2

3

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?

Sign up to request clarification or add additional context in comments.

1 Comment

yes, this works, so I'll mark it as a solution... but it's quite dirty! As my derived objects are about 10, I'll end up having a very dirty collection of POCOs.
2

Change the webmethod to accept an object

[WebMethod]
public string GlobalMethod(object data)

This should resolve the issue you are having.

1 Comment

unfortunately this way I'm able to deserialize the data, but the object is containing only Dictionaries<string,string> ...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.