March 2, 2011
We all know why Cross Site Scripting can be dangerous, but there are many reason that you might need to do it anyway. In our case, we are developing a website widget that can be pasted into any person’s site with just a small javascript snippet. Once the code is installed (similar to Google Analytics), the necessary support files can be loaded and the data for the widget can be retrieved from our servers. Obviously, the domain names will not be the same and this will cause a Cross Domain Request to occur.
Our widget loads the data asynchronously so that the main page rendering will not be blocked. The widget expects a JSON response from our Windows Communication Foundation (WCF) Service which we have full control over.
It turns out that this is very straight forward for every browser except Internet Explorer (shocker) if you’re using jQuery. The basic code for this type of request is the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $.ajax({ type: 'GET' , url: "someurl" , processData: true , data: {}, dataType: "json" , success: function (data) { processData(data); } }); function processData(data){ //Do some stuff with the data } |
The request fires, the response is triggered, and it works across domains. This is an easy and convenient way to process ajax requests. In fact it can be made even more simple using the jQuery wrapper function $.getJSON(); For basic GET requests you can just do the following and get the same result:
1 2 | $.getJSON( "someurl" , function (data){ processData(data);}); |
The $.getJSON() function is shorthand for what we did in the above $.ajax() call.
There is just one major problem, it doesn’t work in Internet Explorer. This is extremely frustrating for a couple reasons, foremost for me is that I never use IE and sometimes I forget to verify things work in it until very late in the game. I checked in Firefox, Chrome, Safari, I guess I just assumed a modern browser like IE 7 or IE 8 would work as well, but alas.
So, how do you make it work in IE? It’s not terribly difficult but it can be frustrating to figure out. It also requires you to force the response type that you would like in WCF because the IE method does not allow you to set any request header information.
WCF in .NET 4.0 is flexible to the calling client; if you let it know you want XML via the content header Accepts:”text/xml” then it will give you XML back. If you tell it you want Accept:”application/json” then the very same service method will return JSON for you. Pretty cool.
In order to get this to work in IE, you need to use a Microsoft proprietary calling method that does not allow you to alter content headers. This means that you will only get XML back from your service unless you create an additional route for JSON or force a JSON response type for everyone. I’m getting a little side tracked here so I will just note the code required to force a JSON response in the WCF service:
1 2 3 4 5 6 | [OperationContract] [WebGet(UriTemplate = "GetLatest/{id}" , ResponseFormat = WebMessageFormat.Json)] public WidgetData GetLatest( string id) { return WidgetData; } |
Back to making this work in IE. You need to check for the browser type and make a separate ajax call if it is of type IE. jQuery makes checking the browser type easy luckily and the annoyingly proprietary equivellent ajax method for making cross domain requests is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if ($.browser.msie && window.XDomainRequest) { // Use Microsoft XDR var xdr = new XDomainRequest(); xdr.open( "get" , "someurl" ); xdr.onload = function () { //parse response as JSON var JSON = $.parseJSON(xdr.responseText); if (JSON == null || typeof (JSON) == 'undefined' ) { JSON = $.parseJSON(data.firstChild.textContent); } processData(JSON); }; xdr.send(); } |
So the full code for both types of requests ends up being:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | if ($.browser.msie && window.XDomainRequest) { // Use Microsoft XDR var xdr = new XDomainRequest(); xdr.open( "get" , "someurl" ); xdr.onload = function () { var JSON = $.parseJSON(xdr.responseText); if (JSON == null || typeof (JSON) == 'undefined' ) { JSON = $.parseJSON(data.firstChild.textContent); } processData(JSON); }; xdr.send(); } else { $.ajax({ type: 'GET' , url: "someurl" l, processData: true , data: {}, dataType: "json" , success: function (data) { processData(data); } }); } |
Update – Allow Origin Headers
You may want to add a response header to the web service response indicating that cross domain requests are OK. The header you want to add to the response is:
1 | Access-Control-Allow-Origin: * |
This will allow any website to perform AJAX requests on this service. You can restrict this to specific domains by replacing the * with the URL you want to allow.
In a .NET WCF service, it’s easiest to add this to the global.asax of your service like so:
1 2 3 4 | protected void Application_BeginRequest( object sender, EventArgs e) { HttpContext.Current.Response.AddHeader( "Access-Control-Allow-Origin" , "*" ); } |