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.
Read the complete post at http://bloggingabout.net/blogs/program.x/archive/2009/03/18/wcf-json-serialization-woes.aspx
Posted
03-18-2009 11:13
by
Nathan J Pledger