WCF -> JSON Serialization woes and a solution

I'm working on improving the performance of my current web application project at various points. As I had already planned for a WCF interface for third-party use, I thought I'd utilise that, exposing objects as JSON-serialized strings usable by jQuery/ASP.NET AJAX.

Little did I realise that when I was coming up with my Data Framework (I chose Entity Framework) I should have thought about aspects at the other side of my project such as the User Interface as well as the more obvious aspects of scalability, performance in relation to a pragmatist view of what is needed in the "real world" (therefore, nHibernate is out).

My WCF service had an exposed service that would return a Client object (actually, an interface IClient) and return it as JSON. Simple.

No, I hit a number of issues with this.

1/ Investigations at StackOverflow indicated that Interfaces cannot be exposed through serialization. Makes sense, really, but dents my idealistic view of presenting interfaces and no conncrete objects to third-parties via my API. So I was going to have to either return my concrete object or reduce my return value to a JSON string, thereby bringing serialization "in house" and not relying on WCF to serialize it for me.

From:

[OperationContract]
[WebInvoke(Method="POST",BodyStyle=WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
IClient GetClientJson(int clientId);

I went to:

[OperationContract]
[WebInvoke(Method="POST",BodyStyle=WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
string GetClientJson(int clientId);

2/ This actually dodged the issue of the attribute configuration on the service itself, which was becoming a nightmare. Tweak it at the server and it breaks at the client, and vice versa. What was:

From:

[OperationContract]
[WebInvoke(Method="POST",BodyStyle=WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
string GetClientJson(int clientId);

I went to:

[OperationContract]
[WebInvoke(Method="POST",BodyStyle=WebMessageBodyStyle.Bare,ResponseFormat=WebMessageFormat.Json)]
string GetClientJson(int clientId);

3/ Next was serializing the object into JSON. I was getting the "The type 'xxx' cannot be serialized to JSON because its IsReference setting is 'True'. The JSON format does not support references because there is no standardized format for representing references. To enable serialization, disable the IsReference setting on the type or an appropriate parent class of the type." exception. As I was essentially getting this at the client, it suggested my WCF endpoint configuration was wrong. Looking deeper, it turns out that Entity Framework objects are marked with IsReference=True, meaning the native DataContractJsonSerializer of WCF cannot serialize Entity Framework objects. I proved this by doing a manual serialization:

string jsonClient;
IClient client = GetClient(7);
DataContractJsonSerializer ser = new DataContractJsonSerializer(client.GetType());
using (MemoryStream ms = new MemoryStream())
{
    ser.WriteObject(ms, client);
    jsonClient = Encoding.Default.GetString(ms.ToArray());
}
return jsonClient;

4/ I needed to serialize using a different serializer, so thought I'd use the ASP.NET AJAX Serializer, which also didn't work, this time falling over the exception "A circular reference was detected while serializing an object of type xxx'." The type it was complaining about wasn't in the object so it was clearly navigating deeper into other objects to find that particular Type anyway.

5/ So now I am left with no other option but to either do it myself or use a third-party library. I'm using Json.NET, which I spotted on Scott Hanselman's blog and seems to be robust enough and simple enough for most purposes. So my code now looks like:

string jsonClient=null;
IClient client=GetClient(1);
JsonSerializer jsonSerializer = new JsonSerializer();
jsonSerializer.Converters.Add(new JavaScriptDateTimeConverter());
jsonSerializer.NullValueHandling = NullValueHandling.Ignore;
jsonSerializer.MissingMemberHandling = MissingMemberHandling.Error;
jsonSerializer.ReferenceLoopHandling = ReferenceLoopHandling.Error;
try
{
     using (StringWriter sw = new StringWriter())
     {
          using (JsonTextWriter jtw = new JsonTextWriter(sw))
          {
                 jsonSerializer.Serialize(jtw, client);
          }
     }
}
catch (Exception ex)
{
       ex = ex; // have a breakpoint here so can inspect exception
}
return jsonClient;

Notice I have set ReferenceLoopHandling to ReferenceLoopHandler.Error. This is to try and catch the same Reference Count issue that ASP.NET AJAX JSON Serialization catches. (Actually this was added after realising I had StackOverflows occurring). Sure enough, I have another Reference Count issue as the Exception does get caught and the error is related to possible infinite recursion.

The JSON.NET Framework allows me to disable serializing potentially problematic objects, but this would require applying these changes to elements of code "Bhind the wall" of my API - and essentially add Web-specific functionality into a domain that is supposed to be platform agnostic. This is not an option for me.

So I appear to be stuck. Other than rendering the JSON myself through a StringBuilder, I'm pretty much stuck on this now. Maybe something will hit me in a flash of inspiration. Until then, it's good ol' StringBuilder for me.

Update: The Solution:

Thanks again due to StackOverflow, (John Saunders and Craig Stuntz) I've figured out how I'm going to do it. It's not as pretty as I would have liked, but pragmatism wins out again.

Here it is from start to finish. My UI generates an event that is picked up by some JavaScript. This runs:

var wcfProxy = new serviceProxy("../api/wcf/ClientBroker.svc/");
wcfProxy.invoke("GetClientJson", { clientId: 7 }, updateClient, updateClientError);

serviceProxy is RickStrahl's WCF wrapper, which has two callbacks, success and error (the last two parameters, respectively). The Invoke method invokes the WCF service and obtains the result. The WCF service is exposed via the Interface:

[ServiceContract(Namespace = "xxxWCF")]
public interface IClientBroker
{
     [OperationContract]
     [WebInvoke(Method="POST",BodyStyle=WebMessageBodyStyle.Wrapped,ResponseFormat=WebMessageFormat.Json)]
     string GetClientJson(int clientId);
}

Note that the Body STyle is Wrapped. I couldn't get it working in Bare mode at all. (Despite telling me it had logged messages in the Windows Event Log, no events were to be found).

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class ClientBroker : IClientBroker 
{
     public string GetClientJson(int clientId)
     {
            string jsonClient=null;
            IClient client = GetClient(clientId);
            var j = new { ID=client.ID, BusinessName=client.BusinessName };
            JavaScriptSerializer s = new JavaScriptSerializer();
             jsonClient=s.Serialize(j);
      }
      return jsonClient;

Notice I am using Anonymous Types to create a new type which is free of Entity Framework idiosyncracies.

This generates a wrapped JSON string:

{"GetClientJsonResult":"{\"ID\":7,\"BusinessName\":\"XYZ Ltd\"}"}

When returned to the client, the success callback is called:

function updateClient(o) {
         eval('var z=' + o');
         alert('BusinessName=' + o.BusinessName);
}

And the Business Name of the requested client is displayed.

It just goes to show that Microsoft may put all the features they like into C#, and you might very well know they are there. But you're not going to use them until you need to use them, and then you need to know that you need to use them! Variant types are, to me, an uncomfortable throwback of those bad VB days but sometimes they can prove useful when it comes to the crunch, I'm just keen on restricting my use of them to the absolute minimum so the very premise of a type-strong language is not lost.

Another stone passed.

 

 

 

 

 

Comments

# re: WCF -> JSON Serialization woes and a solution

Friday, July 24, 2009 12:17 AM by Tyler

Awesome, you save me the headache

# re: WCF -> JSON Serialization woes and a solution

Friday, July 31, 2009 8:28 AM by Ernesto

very useful,thank for shared.

# re: WCF -> JSON Serialization woes and a solution

Thursday, February 04, 2010 4:46 PM by Adam

I also found this useful, but you should know that a var is not the same as a vb variant.  In fact, it has many uses, and I use it all the time.  In fact there are times that you HAVE to use it other than the one you mention above.  Any time an object is generated at runtime, where the type may not be know before hand, you will need to use a 'var.'  Also, it is a strong-data type, in that the compiler knows and enforces type restrictions.  But that type information is generated at runtime.

# re: WCF -> JSON Serialization woes and a solution

Wednesday, March 10, 2010 4:20 PM by Ryan

I had exactly the same problems, but to get around the linq problems, ended up making a serializable class that inherited from the linq to sql class and in its constructor assigned all the values from the base class.

Even with all the notes on the web, its still a difficult exercise to get all this working.

# re: WCF -> JSON Serialization woes and a solution

Friday, July 23, 2010 6:05 PM by Shailen Sukul

I have derived a similar solution.

The interface is defined as: [OperationContract]

       [WebInvoke(Method = "GET", ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]

       object HelloAnonymous();

In the actual implementation, I have used the Newtonsoft serializer:

public object HelloAnonymous()

       {

           return Newtonsoft.Json.JavaScriptConvert.SerializeObject(new { To="You", From="Service" });

       }

You are correct that anonymous types are better for serializing a subset of the type returned by the Entity Framework.

One other thing that I had to do was to implement JSONP callback for my services, as I host my services on a separate domain to my client (MVC2 website). This is so that the same instance can be reused by multiple clients.

My config look like this:

<service name="Ashlen.Portal.Services.TestService" behaviorConfiguration="MEX">

       <endpoint address="json" binding="webHttpBinding" contract="Ashlen.Portal.Services.ITestService"></endpoint>

<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"></endpoint>

</service>

....

<serviceBehaviors>

<behavior name="MEX">

<serviceMetadata httpGetEnabled="true"/>

</behavior>

...

<bindings>

<webHttpBinding>

<binding name="MyAuth">

<jsonpMessageEncoding/>

<security mode="None">

<transport clientCredentialType="None"/>

</security>

</binding>

</webHttpBinding>

To enable a clean solution, I am refactoring my code so that JSON is one of the many endpoints my service provides. It bugs me that I have to hardcode the serialization of the object (Newtonsoft) in the service class, as the correct procedure would be to return the anonymous type and let the endpoint take care of the serialization (ie serialiaze to string for JSON, return the object type for wsHttpBinding, etc).

Keep reading my blog for the label:WCF for updates.

# re: WCF -> JSON Serialization woes and a solution

Sunday, September 26, 2010 4:56 PM by Andriy Mykhaylyuk

One more solution if you want to have better code consistency  is to use JavaScriptConverter which will handle circular reference dependencies and will not serialize such references.

I've blogged about here:

hellowebapps.com/.../producing-json-from-entity-framework-4-0-generated-classes

# re: WCF -> JSON Serialization woes and a solution

Thursday, December 09, 2010 4:57 PM by Tim

never expose your object model(s), EF classes in your case, as a type on a service. You severely tie your hands by doing this, as you saw, because you rely on the client platform being able to interpret/represent your model. Also, the client dictates changes you can make to an internal OM. AND, you have to 'dumb' down your OM to cater to a client. Instead you should be creating a second model for your service, that is simpler, to expose to a consumer. Then you translate from one model to the other. Just because javascript is the client platform doesn't mean we lose all sense of what SOA is.

# re: WCF -> JSON Serialization woes and a solution

Friday, April 01, 2011 3:04 PM by tshirts

Refactoring my code so that JSON is one of the many endpoints my service provides. It bugs me that I have to hardcode the serialization of the object (Newtonsoft) in the service class, as the correct procedure would be to return the anonymous type and let the endpoint take care of the serialization.

Leave a Comment

(required) 
(required) 
(optional)
(required) 
Please add 6 and 6 and type the answer here: