views:

6651

answers:

5

What options are there for serialization when returning instances of custom classes from a WebService?

We have some classes with a number of child collection class properties as well as other properties that may or may not be set depending on usage. These objects are returned from an ASP.NET .asmx WebService decorated with the ScriptService attribute, so are serialized via JSON serialization when returned by the various WebMethods.

The problem is that the out of the box serialization returns all public properties, regardless of whether or not they are used, as well as returning class name and other information in a more verbose manner than would be desired if you wanted to limit the amount of traffic.

Currently, for the classes being returned we have added custom javascript converters that handle the JSON serializtion, and added them to the web.config as below:

<system.web.extensions>
  <scripting>
    <webServices>
      <jsonSerialization>
        <converters>
          <add name="CustomClassConverter" type="Namespace.CustomClassConverter" />
        </converters>
      </jsonSerialization>
    </webServices>
  </scripting>
</system.web.extensions>

But this requires a custom converter for each class. Is there any other way to change the out of the box JSON serialization, either through extending the service, creating a custom serializer or the like?

Follow Up
@marxidad:

We are using the DataContractJsonSerializer class in other applications, however I have been unable to figure out how to apply it to these services. Here's an example of how the services are set-up:

[ScriptService]
public class MyService : System.Web.Services.WebService
{
    [WebMethod]
    public CustomClass GetCustomClassMethod
    {
        return new customClass();
    }
}

The WebMethods are called by javascript and return data serialized in JSON. The only method we have been able to change the serialization is to use the javascript converters as referenced above?

Is there a way to tell the WebService to use a custom DataContractJsonSerializer? Whether it be by web.config configuration, decorating the service with attributes, etc.?

Update
Well, we couldn't find any way to switch the out of the box JavaScriptSerializer except for creating individual JavaScriptConverters as above.

What we did on that end to prevent having to create a separate converter was create a generic JavaScriptConverter. We added an empty interface to the classes we wanted handled and the SupportedTypes which is called on web-service start-up uses reflection to find any types that implement the interface kind of like this:

public override IEnumerable<Type> SupportedTypes
{
  get
  {
    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
      AssemblyBuilder dynamicAssemblyCheck = assembly as AssemblyBuilder;
      if (dynamicAssemblyCheck == null)
      {
        foreach (Type type in assembly.GetExportedTypes())
        {
          if (typeof(ICustomClass).IsAssignableFrom(type))
          {
            yield return type;
          }
        }
      }
    }
  }
}

The actual implementation is a bit different so that the type are cached, and we will likely refactor it to use custom attributes rather than an empty interface.

However with this, we ran into a slightly different problem when dealing with custom collections. These typically just extend a generic list, but the custom classes are used instead of the List<> itself because there is generally custom logic, sorting etc. in the collection classes.

The problem is that the Serialize method for a JavaScriptConverter returns a dictionary which is serialized into JSON as name value pairs with the associated type, whereas a list is returned as an array. So the collection classes could not be easily serialized using the converter. The solution for this was to just not include those types in the converter's SupportedTypes and they serialize perfectly as lists.

So, serialization works, but when you try to pass these objects the other way as a parameter for a web service call, the deserialization breaks, because they can't be the input is treated as a list of string/object dictionaries, which can't be converted to a list of whatever custom class the collection contains. The only way we could find to deal with this is to create a generic class that is a list of string/object dictionaries which then converts the list to the appropriate custom collection class, and then changing any web service parameters to use the generic class instead.

I'm sure there are tons of issues and violations of "best practices" here, but it gets the job done for us without creating a ton of custom converter classes.

A: 

You can use the System.Runtime.Serialization.Json.DataContractJsonSerializer class in the System.ServiceModel.Web.dll assembly.

Mark Cidade
A: 

Don't quote me on this working for certain, but I believe this is what you are looking for.

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public XmlDocument GetXmlDocument()
{
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(_xmlString);
    return xmlDoc;
}
Ty
The WebService is decorated with the ScriptService attribute, so it returns JSON by default, so the ScriptMethod attribute is not necessary.
Scott Nichols
+1  A: 

If you're using .NET 3.x (or can), a WCF service is going to be your best bet.

You can selectively control which properties are serialized to the client with the [DataMember] attribute. WCF also allows more fine-grained control over the JSON serialization and deserialization, if you desire it.

This is a good example to get started: http://blogs.msdn.com/kaevans/archive/2007/09/04/using-wcf-json-linq-and-ajax-passing-complex-types-to-wcf-services-with-json-encoding.aspx

Dave Ward
It requires Anonymous Access to the service which can be an obstacle. (It has been in my case of an internal Windows Auth based App.)
Samuel Kim
Won't really work for us, although we can use 3.x, we are for the time being at least bound to the 2.0 web services. They are fairly extensive and the time to convert is an obstacle, so we were looking for a quick fix to make them more lightweight. Good link though.
Scott Nichols
+1  A: 

If you don't use code-generated classes, you can decorate your properties with the ScriptIgnoreAttribute to tell the serializer to ignore certain properties. Xml serialization has a similar attribute.

Of course, you cannot use this approach if you want to return some properties of a class on one service method call and different properties of the same class on a different service method call. If you want to do that, return an anonymous type in the service method.

[WebMethod]
[ScriptMethod]
public object GimmieData()
{
    var dalEntity = dal.GimmieEntity(); //However yours works...

    return new
       {
           id = dalEntity.Id,
           description = dalEntity.Desc
       };

}

The serializer could care less about the type of the object you send to it, since it just turns it into text anyway.

I also believe that you could implement ISerializable on your data entity (as a partial class if you have code-gen'd data entities) to gain fine-grained control over the serialization process, but I haven't tried it.

ntcolonel
+1  A: 

I know this thread has been quiet for a while, but I thought I'd offer that if you override the SupportedTypes property of JavaScriptConverter in you custom converter, you can add the types that should use the converter. This could go into a config file if necessary. That way you wouldn't need a custom converter for each class.

I tried to create a generic converter but couldn't figure out how to identify it in the web.config. Would love to find out if anyone else has managed it.

I got the idea when trying to solve the above issue and stumbled on Nick Berardi's "Creating a more accurate JSON .NET Serializer" (google it).

Worked for me:)

Thanks to all.

that's basically what we're doing... web config has a generic converter and the SupportedTypes property is overriden. The updates above aren't the exact code we're using, but conceptually it's the same.
Scott Nichols