ASP.Net Web API: Cross-Domain AJAX and Server Techniques: JSONP and CORS   7 comments

AJAX

AJAX calls make the modern web go around.  Interactive web applications make AJAX calls using the HTTP protocol to call HTTP Endpoints on servers to deliver new content to the browser without round tripping the page.  Normally we try to architect Restful endpoints on Web API servers.  Although the X in AJAX once meant XML is now stands for nothing since most AJAX calls over the web package the data as JSON, not XML. JSON eliminates the need for complex proxy code required for SOAP and XML and assures fidelity between the data format and the consuming code (JavaScript) within the browser.  We let the Web be the Web, Restful HTTP rules and life was good. Although one can make AJAX calls in JavaScript using fairly low level calls with vendor specific code most of us use a helper library of one type or another.  I normally use JQuery as my browser abstraction layer in order to minimize browser differences and I find their code to be pretty darn smart.  Much smarter than my JavaScript code.  As we toyed around with this AJAX thing it is a logical extension of the concept to attempt to make AJAX calls to servers NOT in the domain which served up the page to the browser.  That’s is the WEB right we want to connect data from all over the web not just from the server which originally delivered our page.  In formal terms this is know as Cross Domain Resource Sharing and without special cooperation between the browser code, the browser and the server code.

Voice from the future: Brock Allen’s great work on CORS, CORS based CORS Attribute support has now been incorporated into Web API 2.  See here and here for details.

The Server Side

To enable AJAX calls to domains other than the server of the calling page requires that the server support either JSONP or CORS. Of the two methods, CORS is the newest and the preferred method.  However CORS is not supported by all browsers and only on some versions of supporting browsers.  JSONP can be used from all browsers except Opera.  Some history is in order here, the W3C which at one time controlled (or at least managed) these things was, prior to the development of smart phones (i.e. iPhone), pretty slow moving.  Indeed the “Same Origin Policy” expletively forbad cross domain resource sharing.  Although there was quite a bit of pressure since the turn of the century for cross domain AJAX no new spec was forth coming. To work around this clever coders (on the server and browser sides) took what was once a security  exploit (the “cross-site request forgery”) and turned it into a virtue (JSONP).  Eventually the W3C developed a “Working Draft” on CORS (the original draft is dated March 2009 and the most recent draft is April 2012).  As this draft stabilized,  server code could be developed and browser vendors could enable CORS support.  OK, lets look at some server code.  I am going to use examples for GET request with NO security (clearly the simplest case, I will follow up with folding POST and security in a later blog entry).

Controllers On ASP.Net Web API

A bare bone Web API Get controller for ASP.Net MVC 4 might look like:

DeadBase2.DAL.DeadBase2Entities  _DBContext = new DAL.DeadBase2Entities( );

public IEnumerable JSONP( string MM, string DD )
{
return _DBContext.ViewConcerts.ToList( )
.Where( c => string.Compare( c.YYYYMMDD.Substring( 4, 4 ), MM + DD ) == 0 ).OrderBy( c => c.YYYYMMDD );
}

This will return JSON if the caller includes the media type header with the argument:

text/javascript

In this form the controller can return JSON but it does not yet support COR or JSONP.

We can limit the controller to HTTP GET requests by adding the attribute

[System.Web.Mvc.AcceptVerbs( HttpVerbs.Get )]

There have been some attempts to develop an attribute to limit controller methods to AJAX only calls.  Since these are usually based on examining header values I take of dim view of this since headers from the caller can be faked and falsified.

Let’s expand the controller model to include the ability to return HTTP Error Codes

public HttpResponseMessage<ienumerable> JSONP( string MM, string DD )
{
HttpResponseMessage<IEnumerable<DAL.ViewConcert>> msg;
_Concerts = _DBContext.ViewConcerts.ToList( )

                   .Where( c => string.Compare( c.YYYYMMDD.Substring( 4, 4 ), MM + DD ) == 0 ).OrderBy( c => c.YYYYMMDD );
if ( _Concerts.Count( ) == 0 )
{
msg = new HttpResponseMessage<IEnumerable<DAL.ViewConcert>>( System.Net.HttpStatusCode.Forbidden );
}
else
{
msg = new HttpResponseMessage<IEnumerable<DAL.ViewConcert>>( _Concerts );
}
return msg;
}

These changes will allow us to  return HTTP error codes to AJAX and CORS calls.  JSONP callers will not (more on this below). receive these codes.

Enabling CORS On The Server

To enable CORS on the server side all we need to doo is provide appropriate W3C recommended server-side headers to the response just prior to returning from the method.  The most important of these is the “Access-Control-Allow-Origin”.  To allow all CORS callers the argument value is the wild card:

Access-Control-Allow-Origin: *

to limit the callers to specific cross-domain callers, like:


 

Access-Control-Allow-Origin: http://example.com:8080 http://foo.example.com

In our model method controller  we simply add the following line just prior to the return statement:

 msg.Headers.Add( “Access-Control-Allow-Origin”, “*” );

Browser Side Code for CORS

Simple, easy and fun.  This header allows the browser code to allow the return data stream to passed to your application code (your JavaScript).  Not all browsers support the working draft.  Currently the list includes:

Gecko 1.9.1 (Firefox 3.5,[3] SeaMonkey 2.0[4]) and above.

WebKit (Initial revision uncertain, Safari 4 and above,[1] Google Chrome 3 and above.

MSHTML/Trident 4.0 (Internet Explorer 8 and Explorer 9) provides partial support via the XDomainRequest object.[1]

Assuming you are working with a conformant browser a CORS call in JQuery looks like:

function CallAJAX (){
$.ajax(
{
type: “GET”,
url: “
http://localhost:8080/deadbase2api/api/Concerts/MMDD/12/31″,
dataType: “json”,
success: function (data) { alert(‘Success AJAX’); alert(‘Objects Returned: ‘ + data.length); },
error: function (XMLHttpRequest, textStatus, errorThrown) { alert(“Error AJAX”); }
}
);
}

See this link for more JQuery details.  If you are not using IE8 or IE9 that’s it. Done.  If you are using IE8 or IE9 things an additional steps are required.  Since IE8 and IE9 use XDomainRequest object for CORS calls JQuery $.ajax calls will not work out of the box.  You must include a JQuery plug-in (Get a local copy from here).  Include this file after you have loaded the JQuery library (we load our JQuery at run time from a Google edge server).  Now the above $.ajax code will work on all CORS supporting  browsers.  Two more notes on IE8 and IE9, in my tests IE9 and IE8 will not trapped HTTP errors in CORS calls (strange but true).  The somewhat mythical IE10 reportedly will not use XDomainRequest object and will be conformant with the WebKit model in which case the extra JQuery plug in will not be necessary.  We shall see.

Enabling JSONP on the Server

A cross-domain JSONP AJAX call includes a query string argument named “callback” with an argument determined by the caller.  In our example code the calling might compose something like this:

http://localhost:8080/deadbase2api/api/Concerts/MMDD/12/31?callback=mycallbackfunction

and pass the proper JSON media type header (text/javascript).  It’s the servers job to do two tasks:

  • process the call and output JSON formatted data; and
  • wrap the JSON data in the argument of the callback function like this: mycallbackfunction({JSON DATA goes here})

Inside of the controller method we could read the argument of the query string argument and generate the necessary string.  A slightly more elegant approach is to add JSONP formatter to do this for all method calls which meet the criteria for a JSONP response. Alex Zeitler has contributed to GitHub an excellent JSONP formatter which can be hooked into the Web API system.  Get it here.  His blog post on the formatter is here.  This guy rocks.  Pull his code into a code file in your Web API project (change the namespace if you need to).  Add the formatter to your application_start event in global.asax file.  My application_start code looks like this:

protected void Application_Start( )
{
AreaRegistration.RegisterAllAreas( );

          RegisterGlobalFilters( GlobalFilters.Filters );
GlobalConfiguration.Configuration.Formatters.Insert( 0, new DeadBase2.Formatters.JsonpMediaTypeFormatter( ) );
RegisterRoutes( RouteTable.Routes );

          BundleTable.Bundles.RegisterTemplateBundles( );
}

This formatter is at the model level, that is it will apply to all controller methods which the formatter tests as qualifying for JSONP formatting.

Browser Side Code For JSONP

OK now you are good to go to respond to JSONP requests. All browsers that we know of, except Opera, support JSONP calls for cross-domain resource calls.  In JQuery the call looks like:

function CallJSONP() {
$.ajax(
{
type: “GET”,
url: “
http://localhost:8080/deadbase2api/api/Concerts/MMDD/12/31″,
dataType: “jsonp”,
success: function (data) { alert(‘Success JSONP’); alert(‘Objects Returned: ‘ +data.length); },
error: function (XMLHttpRequest, textStatus, errorThrown) { alert(“ERROR JSONP”); alert(textStatus); } //see note below
}
);
}

JQuery will generate the appropriate query string and provide a unique random name to use as the argument for the callback key in the query string.  Note that JQuery uses “jsonp” as the argument for “datatype” it will use this key to generate a query string which includes a random argument for the callback function, provide a json media type header and handle decoding the response and calling the success function.  For most browsers HTTP errors of all types will not be trapped and the error function will never be called (this includes 404 errors!).  Internally generated errors with the controller method (like the System.Net.HttpStatusCode.Forbidden in the model code above) will generate a trappable error in IE8 and IE9 only – however the error returned is NOT the HTTP Error code but a strange “parse error” message.  All of this weirdness about error code trapping is caused by the browser code not the Server code and not the JavaScript code.  A review of the wire traffic using Fiddler (you do use Fiddler for debugging don’t you) shows that the proper HTTP Error codes are returned to the browser, the browser just thrown them away.  AJAX calls which are not cross domain do receive the HTTP status codes properly. Strange.

Cross Domain Error Response Summary:

Chrome IE9 FoxFire Safari
CORS Trapped Not Trapped Trapped Trapped
JSONP Not Trapped Trapped* Not Trapped Not Trapped

Ok. That’s All Folks.

7 responses to “ASP.Net Web API: Cross-Domain AJAX and Server Techniques: JSONP and CORS

Subscribe to comments with RSS.

  1. For enabling jsonp on the server; does the uriformatextension file not go in the server side web service or does it get added to the web application?

  2. It’s a shame this site is sooo hard to read. Poor contrast (blue on black) small fonts on code examples and lots of extraneous clutter.

  3. Gee, and I thought it looked ‘cool’!

  4. How can we do a post method with jsonp ? please

  5. In IE 8,9 used XDomainRequest and impossible send ajax-call to server with cookie or requestverification header. And we can’t secure our requests/responses. This is very bad. Have you any ideas?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 118 other followers

%d bloggers like this: