Tags: , , , | Categories: REST, REST, WCF, WCF Posted by AlexanderZeitler on 7/26/2011 10:15 AM | Comments (4)

When it comes to consuming a Web API with jQuery, you’ll want to use JSON as it’s lightweight and easy to handle with jQuery.

As always, when something seems to become a silver bullet there arise some new problems on the horizon.

And so do jQuery and JSON when trying to receive some JSON from a foreign domain with jQuery.getJSON.

Lets assume your domain is http://localhost:34847  and you want to get JSON from http://localhost:40553 using this little piece of jQuery:

<script type="text/javascript">
	jQuery.getJSON("http://localhost:40553/session/1",
function (data) {
	alert("Title: " + data.Title);
});
</script>

You’ll simply get: nothing.

Why?

jQuery.getJSON is a wrapper for jQuery.Ajax and you could also write your request like this:

<script type="text/javascript">
	jQuery.ajax({
		type: "GET",
		url: 'http://localhost:40553/session/1',
		dataType: "json",
		success: function (results) {
			alert("Success!");
		},
		error: function (XMLHttpRequest, textStatus, errorThrown) {
			alert("error");
		}
	});
</script>

When executing this you’ll get an alert with “error”.

Again: why?

This is because you’re trying to do cross domain scripting which is not allowed.

To get a detailed description of the problem and it’s solution please read this.

JSONP ftw!

To make it possible to use jQuery Ajax calls receiving JSON you’ll have to use JSONP (JSON with Padding) which embeds a dynamically created <script> element containing the JSON object(s)into your document.

jQuery supports JSONP using jQuery.getJSON the following way:

<script type="text/javascript">
	jQuery.getJSON("http://localhost:40553/session/1/?callback=?",
function (data) {
	alert("Title: " + data.Title);
});
</script>

When getting an URI like the above jQuery replaces the last “?” with the name of a dynamically created callback method name and embeds this method into the aforementioned script tag.

A typical JSONP request looks like this:

http://localhost:40553/session/1?callback=jsonp1311664395075

The name of the callback function has to be returned by your service on the server and if it matches the name of the one jQuery sent to it you’ll receive the JSON in your script for further handling.

The JSONP response to our request could look like this (JSONP without the HTTP headers):

jsonp1311664395075({"Title":"Hello World with WCF Web API"})

Creating JSONP using the WCF Web API

To make our service return JSONP or more exactly: JSON prefixed with the callback method name we’ll have to get the value of the “callback” query string parameter and append our generated JSON element.

Sounds like we’ll have to implement another Formatter in WCF Web API.

No - this won’t work.

There are two reasons for this:

First problem is: we won’t receive an unique accept type like “application/jsonp” our Formatter could respond to. Instead jQuery creates an accept header of “*/*” which is the worst case. Instead jQuery assumes that your URI is specific to JSONP requests and your service can negotiate that you're requesting some JSONP from that specific URI – e.g. something like this:

http://localhost:40553/session/1/json?callback=jsonp1311664395075

The second problem is: even if we could find a way to route or request into our JSONP Formatter, we won’t be able to access the request parameters as there’s no way to access the HttpContext.Current inside formatters in Preview 4 of WCF Web API (this will be fixed in a later preview).

Ok. So we can’t handle JSONP with WCF Web API. That’s it – have a nice day…

Winking smile

Of course there’s a way to deal with JSONP using WCF Web API:

First, lets take a look at the “ContactManager_Advanced” sample included in the WCF Web API Preview 4 Download.

It contains a UriFormatExtensionMessageChannel(.cs) implementation which allows you to rewrite URIs like

http://localhost:12345/sessions/1/json

to

http://localhost:12345/sessions/1

and set the Accept header to “application/json”.

Grab this file and include it to your project.

To wire things up add this to your configuration in global.asax.cs:

var configuration = HttpHostConfiguration.Create()
	.SetResourceFactory((serviceType, instanceContext, request) => container.Resolve(serviceType), null)
	.AddMessageHandlers(typeof (UriFormatExtensionMessageChannel));

var mappings = new List<UriExtensionMapping>();
			mappings.AddMapping("json", "application/json");
			this.SetUriExtensionMappings(mappings);

Now we're almost done. To prefix our response body with the callback method name we just implement a HttpResponseHandler which is another example for the WCF Web API extensibility.

HttpResponseHandlers act after the Response has been created – perfect for our needs.

public class JsonpResponseHandler : HttpOperationHandler<HttpResponseMessage, HttpResponseMessage> {
	public JsonpResponseHandler()
		: base("response") {
	}

	public override HttpResponseMessage OnHandle(HttpResponseMessage response) {
		
		// anyone requesting some JSON?
		var accept = response.RequestMessage.Headers.Accept;
		if (accept.Contains(new MediaTypeWithQualityHeaderValue("application/json"))) {
			var sb = new System.Text.StringBuilder();
			var queryString = HttpUtility.ParseQueryString(response.RequestMessage.RequestUri.Query);
			
			// or even JSONP?
			if(!string.IsNullOrEmpty(queryString["callback"])) {
				var callback = queryString["callback"];
				
				// wrap the JSON into some JSONP
				sb.Append(callback + "("+response.Content.ReadAsString()+")" );
				response.Content = new StringContent(sb.ToString());
				
				// fix the Content-Type for the response
				response.Content.Headers.Clear();
				response.Content.Headers.AddWithoutValidation("Content-Type","application/json");
			}
		}

		return response;
	}
}

The code above is pretty simple and should be almost self describing:

  1. Our JsonpResponseHandler expects a HttpResponseMessage for input and returns it.
  2. If someone requests some JSON and
  3. has set a callback method name in our query string (which indicates the need for JSONP)
  4. wrap the JSON content into the JSONP callback stuff and
  5. fix the response headers
  6. return the pimped HttpResponseMessage

To get the JsonpResponseHandler up and running, just add it to your configuration in global.asax.cs:

configuration.AddResponseHandlers(c => c.Add(new JsonpResponseHandler()), (s, o) => true);

When calling jQuery.getJSON from the beginning against the following URI

http://localhost:40553/session/1/json?callback=?

we get the following result:

result of JSONP

Thats' it! Have fun with JSONP Winking smile

Update: You can get the JsonpResponseHandler and the UriFormatExtensionMessageChannel using NuGet as it now part of the WebApiContrib project:

WebApiContrib

DotNetKicks-DE Image

Comments

Comments are closed