Note: This post is part of a long-running series of posts covering the union of jQuery and ASP.NET: jQuery for the ASP.NET Developer.
Topics in this series range all the way from using jQuery to enhance UpdatePanels to using jQuery up to completely manage rendering and interaction in the browser with ASP.NET only acting as a backend API. If the post you're viewing now is something that interests you, be sure to check out the rest of the posts in this series.
Work on client-side applications long enough and it’s just about inevitable that you’ll eventually want to make an AJAX request that breaches the browser’s XMLHttpRequest security restrictions. Limitations on cross-domain requests are great when they’re preventing malicious sites from malfeasing, but are a thorn in the side when they complicate your legitimate applications.
Traditionally, direct communication across the same-origin boundary required using a rickety (though clever) workaround called JSONP. JSONP is a reasonable compromise if all you need to do is make blind requests to a third-party API like Twitter, but comes up short if you need to use any HTTP verb other than GET. Of course, that’s a deal-breaking issue when you’re working with ASMX ScriptServices or ASPX page methods.
Luckily, a relatively new feature has been making its way into browsers which provides a robust solution to the cross-domain AJAX problem: CORS.
In this post, I’m going to show you how to recognize exactly which requests are cross-origin, how to enable CORS for your ASP.NET site, and the extra configuration necessary when you’re working with ASP.NET’s JSON-enabled services.
Before we get started, I want to emphasize that this approach won’t work with any version of IE prior to IE10. If supporting older versions of IE is a requirement in your target environment, you’re stuck with something like JSONP or a server-side proxy. This will work in any version of IE if Chrome Frame is installed and enabled by your site/server though.
Understanding what constitutes a different “origin”
When it comes to making cross-origin AJAX requests, it’s helpful to understand which requests actually are cross-origin. Of course, a request from a page served under one domain to a resource on an entirely different one definitely falls under the umbrella of cross-origin:
However, the common moniker for these requests, cross-domain, tends to be misleading. While a request from one domain to another is obviously cross-origin, browsers are much more picky than that.
For example, a request from
bar.domain.com is just as much cross-origin request as one to
abc.com. That’s not quite as intuitive as the traditional cross-domain scenario, but requires the same consideration.
A less obvious troublemaker is making a request from
localhost:8081, which XMLHttpRequest’s security restrictions will also deny you from making. That’s a particularly troublesome scenario for ASP.NET developers since Visual Studio is all too happy to help you spin up a separate services/API project almost transparently hosted at its own separate port.
The easiest way to determine whether a particular request will fall under the umbrella of cross origin resource sharing is to compare the portion of both addresses appearing before the first forward slash. Any difference before the first forward slash, including the port, protocol (HTTP vs. HTTPS), and sub-domain, will thwart a traditional XHR-based AJAX request.
Enabling CORS for an ASP.NET site
Though the conventional wisdom about cross-domain or cross-origin requests has long been JSONP or nothing, a solution called Cross-Origin Resource Sharing has gained broad support in modern browsers over the past couple years. This standard defines a set of HTTP headers that a server can use to instruct browsers that it’s okay for certain XMLHttpRequest requests to that server to cross the same-origin boundary.
In the broadest case, enabling CORS for an ASP.NET site is simple. The enable-cors.org site has handy list of concise examples of how to configure a variety of servers for CORS, including IIS. For sites hosted on IIS7.0+ and Integrated Pipeline, enabling CORS is as simple as adding this to the site’s web.config:
<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> </customHeaders> </httpProtocol> </system.webServer>
That simple change gets you half way there. A regular GET request for an ASPX page’s HTML would work now, for example. Unfortunately, the Content-Type necessary for working with ASMX ScriptServices throws a new wrench in the works:
As it turns out, requests to ASP.NET’s JSON-enabled endpoints require one last configuration step to allow the request to go through unimpeded.
Access-Control-Allow-Origin header allows basic cross-origin access, but calling ASP.NET services like ASMX ScriptServices, ASPX page methods, and WCF services with AspNetCompatibilityRequirements enabled requires jumping through a few more hoops. Namely, sending a specific Content-Type header of
One of the security precautions that the CORS specification includes is that browsers must tightly control what’s sent along with these cross-origin requests. Among the items that must be explicitly allowed: any but the most basic HTTP headers, including
In order for CORS requests to specify their Content-Type, your site needs to respond with one additional CORS header:
That brings the web.config modifications necessary to this:
<system.webServer> <httpProtocol> <customHeaders> <add name="Access-Control-Allow-Origin" value="*" /> <add name="Access-Control-Allow-Headers" value="Content-Type" /> </customHeaders> </httpProtocol> </system.webServer>
And finally, it’s possible to use the same, familiar jQuery
$.ajax() syntax to request our service from same- and cross-domain origins alike:
Even as we’re teetering at the precipice of 2012, CORS is still a fairly new feature and lacks ubiquitous support. IE10 should support CORS as fully as Chrome and Firefox already do, but the XDomainRequest implementation in earlier versions of IE does not (to my knowledge) support
Access-Control-Allow-Headers, which makes it all but useless in this scenario.
If you’re comfortable leaving the comfy confines of XMLHttpRequest and jQuery’s
$.ajax, a more cross-browser friendly solution does exist: easyXDM. I haven’t personally used easyXDM beyond a bit of experimentation, but the approach it takes in older browsers is one that has been proven to be solid by similar libraries like Socket.IO.
However, if you’re working in an environment where you can mandate Chrome (or Chrome Frame in IE9-) and/or Firefox as the browser of choice, CORS is an invaluable addition to your bag of tricks (I feel like I’ve typed that phrase in another post recently…). I hope this post will help you get your feet wet with this new technology which seems to get more lip service than actual usage.
Get the source
If you’d like to browse through a complete working example of what’s been covered in this post, take a look at the companion project at GitHub. Or, if you’d like to download the entire project and run it in Visual Studio to see it in action yourself, grab the ZIP archive.