Search Wiki:
Notes on the v0.3 update
The last update includes the following tweaks:
  1. Always respond with UTF-8 encoded text to enhance compatibilty with user-agents that indicate different preferences for character encoding
  2. Use text/javascript instead of application/json as the response content-type, as some browsers don't like application/json going into <script> tags
  3. Support both application/json and text/plain for "accept" types to allow for /$count requests

About this JSONP extension for Data Services

JSONP is a common way of making data accessible in client-side mashups even when the requests need to be cross-domain.

While the current version of the ADO.NET Data Services framework does not support this, it's possible to build it on top. There are a couple of ways of doing this. This example shows what's probably the simplest way. There is some downsides to this approach, but overall is the most straightforward path to get there.

The default transport layer for Data Services is WCF, which has a many extensibility points across the stack. For the case of JSONP support, IDispatchMessageInspector comes in handy.

There are two things needed to support JSONP properly:

1. The ability to control the response format. Data Services uses standard HTTP content type negotiation to select what representation of a given resource should be sent to the client (e.g. JSON, Atom). That requires that the caller can set the Accept request header, which is not possible when doing the JSONP trick (which basically just uses <script> tags). We need to add the ability to use the query string in the URL to select format. (e.g. /People(1)/Friends?$orderby=Name&$format=json).

2. A new option to wrap the response in a callback if such callback was provided in the request (also in the query string). For example /People(1)/Friends?$orderby=Name&$format=json&$callback=loaded.
What we'll do is register a message inspector and adjust the request/response when we see these new options coming in.

In order to support the $format=json option we can intercept the message before it gets dispatched to the Astoria runtime, at the IDispatchMessageInspector.AfterReceivedRequest method. If we see the query string option then we'll a) strip it out from the URL so Data Services does not generate an error and b) change the “Accept” header to “application/json”, so the rest of the system just thinks that the client asked for a JSON response in the first place.

For the second part, where we need to wrap the response into a Javascript call if the $callback option was used, we have the IDispatchMessageInspector.BeforeSendReply method which gives us the perfect spot to rewrite the response. One unfortunate side-effect of this is that the response will get buffered and re-encoded; that said, in many cases this won't make any noticeable difference.

Finally, we need to register the interceptor with WCF's dispatchers. For that we create an attribute that implements IServiceBehavior, so we get called during service initialization. When we get called we can register our message interceptor.

The net effect is that if you include this code in your project, you just need to add a single attribute to your Data Service to make it support JSONP:

public class SampleService : DataService<ContactsData>
// your service code here...

Once that's in place you can use JSONP by adding $format and $callback to URLs, for example:

Of course, you can still use all the other Data Services URL options in addition to these.

NOTE about this example
This is just an example. It's just quickly tested, without covering all scenarios. Also, a number of simplifications in the example may need to be addressed if this is used in real applications. For example, the response re-write code assumes that the output encoding is UTF-8 (the default), but the client and server might have negotiated a different character encoding.
Last edited Jun 24 2011 at 7:23 PM  by pabloc, version 6
andybooth wrote  May 26 2009 at 5:49 PM  
From (Line 61): string content = JSONPSupportInspector.encoding.GetString(Convert.FromBase64String(reader.Value));
To (Line 61): string content = JSONPSupportInspector.encoding.GetString(reader.ReadContentAsBase64());

I believe the update above, or similar, ensures that the full JSON string is decoded - more complex entity graphs (using $expand) and larger datasets appeared to miss off "}" and "]" characters, and return an invalid JSON string.

Would love to see an updated and more supported JSONP Data Services sample, as is very useful on the consumer oriented sites I'm working on.

Arpita wrote  Jun 12 2009 at 5:37 AM  
Hey, I am looking for JSONP since last two days, can u update above so that a service supports both XML as well as JSONP format?

bmiller wrote  Dec 18 2009 at 8:11 PM  
andybooth is correct. Line 61 should be altered as he described.

I needed to change the contenType of the response from "application.json" to "text/javascript". Adding this code at the end of the BeforeSendReply method did the trick:

var replyProperties = (HttpResponseMessageProperty)reply.Properties[HttpResponseMessageProperty.Name];
replyProperties.Headers["Content-Type"] = replyProperties.Headers["Content-Type"].Replace("application/json", "text/javascript");

Many thanks to pabloc for publishing!

pabloc wrote  May 7 2010 at 1:50 AM  
Thanks for pointing out the glitch in the code. I fixed it and published a new version with the correction in it.

ALISTAIR77 wrote  Dec 7 2010 at 3:55 PM  
Calling $count (eg$count) results in an "unsupported media type" exception. I guess this is due to the count being returned in plain text. Haven't figured out how to fix yet ;-). Thnx for the good work.

ALISTAIR77 wrote  Dec 7 2010 at 4:09 PM  
Changing line 41 from: httpmsg.Headers["Accept"] = "application/json"; to httpmsg.Headers["Accept"] = "application/json,text/plain"; does the trick.

losthobbit wrote  May 17 2011 at 10:43 AM  
If you stumbled upon this page and don't have the code for the JSONPSupportBehavior, get it here:

kakone wrote  Apr 17 2012 at 7:56 AM  
It doesn't work anymore with WCF Data Services 5.0. The following line :
httpmsg.Headers["Accept"] = "application/json, text/plain;q=0.5";
must be changed by
httpmsg.Headers["Accept"] = "application/json;odata=verbose";

hrady wrote  May 17 2012 at 2:00 PM  
OR you could add both for larger support :
httpmsg.Headers["Accept"] = "application/json, text/plain;q=0.5,application/json;odata=verbose";

ScottStafford wrote  Jan 25 2013 at 6:00 PM  
NOTE: All of this is moot >= WCF DS 5.1, which supports $format and $callback natively, as well as JSON Light.

Page view tracker