Ah, here's another fun one. I've been working on cleaning up some of the DevConnections Demos from last year with jQuery 1.2.6 and the release version of jQuery.ui 1.5 as I had previously been using beta versions and in between versions of jQuery that came with the UI beta.
One of my demos has been an interop Stock Portfolio example that shows stock portfolios as well as stock quote lookups. These demos actually worked fine before when I used them for the conference and testing in various scenarios, but when I ran through them again today I noticed that quite frequently the application would lock up. One of the things the sample does is do stock symbol lookups so if you type in a letter it tries to immediately retrieve the symbol's company and display it - requests can be rapid and so it can get very difficult to debug.
After a lot of trial and error I traced this back to the case when an invalid stock quote was requested and either a null reference or an empty object was returned. Whenever this happened the server simply hangs up and is not returning a response. No error, no message, but requests just hanging. Playing around with this a little more I found two issues which apparently have their root in WCF 3.5 JSON HTTP behaviors that are, uhm less than optimal.
Null Results
The first issue I ran into had to do with returning null objects from method calls. I was getting stock quotes and if a quote couldn't be found returning null values. However, I figured out that WCF returned the returned null value to my jQuery client as an empty string or no value at all (ie. 0 bytes).
This turns out to be a behavior problem in WCF when using Bare messages when communicating with the server:
[OperationContract]
[WebInvoke(Method="POST",
BodyStyle=WebMessageBodyStyle.Bare,
ResponseFormat=WebMessageFormat.Json
)]
StockQuote GetStockQuote(string symbol);
If you use WebMessageBodyStyle.Bare and return null apparently WCF doesn't serialize the value but instead returns no data. It doesn't throw or cause an error as it does on hard errors, but it also returns no data. This issue is isolated to WebMessageBodyStyle.Bare, so if you're returning data with the default WebMessageBodyStyle.Wrapped (MS AJAX style) null is properly returned as aJSON encoded null string, but you also get the extra object wrapping on output and requirements to pass parameters as a named object map for each parameter.
The empty value bonked my client side JSON parser because no data is not a valid JSON value and so the parser (a slightly modified JSON2.js) failed. This is no big deal and easy to fix, but it still sucks royally - if I return null I'd expect WCF to serialize a null properly back as JSON regardless of whether it thinks a REST style result for null should be no data. That's what I specify the data type for and JSON obviously has a null value representation.
This was minor annoyance and easily fixed, although also a little tricky to catch.
As I've mentioned in various posts and lectures using Bare JSON messaging just has too many inconsistent behaviors like this null representation, the inability to capture errors on the client effectively (they basically throw HTML Yellow screen of Death errors) and the difficulty in dialing in the messaging for multiple format options just make them a bad choice. The REST based JSON interfaces need serious fixes before this stuff is ready for production usage, IMHO.
For AJAX scenarios it's simply easier to go with the MS AJAX style messaging that wraps messages regardless whether you use the Microsoft Service Proxies or another tool like jQuery. If you use the latter you can find more info on doing so in a previous post. Using that mechanism it's easy to switch between bare and wrapped modes from the client code, but in my own apps I've just gone down the route of the slightly more wordy 'wrapped' syntax because Microsoft properly handles the RPC method call scenarios and error handling which is otherwise impossibly lame. Bare sounds interesting and easier but in the end without consistency it's not the right call for me.
Date Serialization Problems
The next problem is much more insidious because it hangs ASP.NET solid. After fixing the null issue I realized that requests were still locking up the ASP.NET application when hitting the GetStockQuote() method above, but again only in situations when an invalid quote was returned and even then not every time. GetStockQuote actually would never return null, but rather return an 'empty' StockQuote object that includes a message that can be displayed (ie. symbol not found with the symbol name).
However, whenever this happened no result went back to the client and the request would hang.
It turns out that WCF doesn't deal very well with small dates while serializing service method result data. Specifically, I've run into a nasty problem with returning DateTime.MinValue. If you want to lock up an ASP.NET application that services WCF data data via JSON create a method like the following:
[ServiceContract(Name="StockService",Namespace="JsonStockService")]
public interface IJsonStockService
{
[OperationContract]
[WebInvoke(Method = "POST",
BodyStyle = WebMessageBodyStyle.Wrapped,
ResponseFormat = WebMessageFormat.Json
)]
DateTime GetDate();
}
and implement the method like this in the implementation class:
public DateTime GetDate()
{
return DateTime.MinValue;
}
When you do this you'll find that the service call doesn't just fail, it actually locks up the entire application if the failures occur a few times in succession. The following trace in FireBug demonstrates what this looks like:
The first to XHR hits are successful hits against another request, then the two test hits against the GetDate() method with the MinValue date result. Note that the result byte count is ? - it's basically a request that's not returning and instead timing out. Note also that the final AJAX call - calling the GetStockQuote method again - also fails. Opening another browser and trying to access any ASP.NET page also fails although this doesn't immediately occur. Bonk - the ASP.NET application is locked.
My first thought here was that WCF fails serialization of a date smaller than 1-1-1970, because this is the base date that JavaScript uses for any date math. That date is the 0 milliseconds and all other dates are calculated by adding milliseconds to it. I tried the following return just for kicks:
public DateTime GetDate()
{
return new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc);
//return DateTime.MinValue;
}
and that actually works, but switching back to returning DateTime.MinValue again fails immediately and hangs the app.
What's odd is that if leave the app sitting for a while, eventually - after 3 minutes or so - the ASP.NET app starts responding again and any stacked up requests complete.
[updated]
It looks like the problem lies with the DataContractJsonSerializer which also bombs on DateTime.MinValue as the following example demonstrates:
protected void Page_Load(object sender, EventArgs e)
{
DateTime value = new DateTime(2, 1, 1);
string json = this.ToJson(value);
Response.Write("<pre>" + Server.HtmlEncode(json) + "</pre>");
}
public string ToJson(object value)
{
DataContractJsonSerializer ser = new DataContractJsonSerializer(value.GetType());
MemoryStream ms = new MemoryStream();
ser.WriteObject(ms, value);
return = Encoding.Default.GetString(ms.ToArray());
}
which also fails with:
Specified argument was out of the range of valid values.
Parameter name: value
in the call to WriteObject. It's interesting to experiment with the dates here. MinValue fails, but:
DateTime value = new DateTime(1, 1, 1, 0, 0, 0,DateTimeKind.Utc);
works. Remove the UTC flag and it also fails.
Nasty little serialization bug that.
Workaround?
The workaround is ugly - basically set the date and time to an explicit value on any defaulted values or members that make it to the client. I tend to use a static date value that represents the JavaScript 0 date:
public static DateTime DAT_BASEDATE = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
which I can use as a sort of custom MinValue date:
[DataMember]
public DateTime LastQuoteTime
{
get { return _LastQuoteTime; }
set { _LastQuoteTime = value; }
}
private DateTime _LastQuoteTime = DAT_BASEDATE; // DateTime.MinValue;
and that works just fine. Alternately if you run into this issue and you don't want to muck around with the base type you can also do this right before you return a result from a service implementation method:
public StockQuote GetQuote(string symbol)
{
StockQuote quote = new StockQuote();
quote.LastQuoteTime = StockQuote.DAT_BASEDATE;
return quote;
}
Ugly I know but it works. Fun ain't it?
Other Posts you might also like