JavaScript Object Notation For Cross Domain Web Requests

In attempting to use the Microsoft AJAX Library 1.0 in a MSHelp2 collection (to be viewed in the Microsoft Document Explorer) I ran into the same origin policy (SOP) restrictions on the XMLHttpRequest object. I cannot make web service data requests to another server from a page in compiled help without getting a JavaScript error message: "Access is denied".

 Access Is Denied

Even requests to my localhost web server fail. I believe this is due to the strange protocol MSHelp2 uses (i.e. ms-help://) which cannot match the web service protocol, therefore violating same origin equality. This eliminates the use of locally running proxies to retrieve data from remote web services. It is essentially impossible to make any use of the XMLHttpRequest object from a help collection.

The one solution I have found to this problem is to use JSON and dynamic script tags as described on the Yahoo! Developer Network. This is a pure client-side JavaScript solution which does not make use of the XMLHttpRequest object or any proxy servers. I have found two major sources of web services that can return JSON data with a callback function; Yahoo! and Google. For information on the Google Data APIs visit http://code.google.com/apis/gdata/json.html. Since YouTube is now owned by Google, there happens to be a Google data feed for getting information about YouTube videos. I was able to use the JSON version of this data feed to display my video favorites within my custom help collection:

YouTube Video Favorites

This works because the JSON data is being pulled in by a script block with its source set to the web service supplying the data:

<script type="text/javascript" src="http://gdata.youtube.com/feeds/users/robrobbins/favorites?start-index=1&max-results=25&alt=json-in-script&callback=ws_results"></script>

Note that the data format is specified as JSON rather than the usual XML by the query string parameter json-in-script and the callback function will be ws_results. Then there is another script block for the callback function which can reference the web service data as a JavaScript object:

function ws_results(obj) {
//alert(obj.feed.entry.length);
// create paragraph
var paragraph = document.createElement("p");
var textnode = document.createTextNode("Total Results: " + obj.feed.entry.length);
paragraph.appendChild(textnode);
// create unordered list
var unorderedList= document.createElement("ul");
// create list items
for (var i = 0; i < obj.feed.entry.length; i++) {
var header = document.createElement("h2");
var itext = document.createTextNode(obj.feed.entry[i].title.$t);
header.appendChild(itext);
unorderedList.appendChild(header);
var div = document.createElement("div");
div.innerHTML = obj.feed.entry[i].content.$t;
unorderedList.appendChild(div);
}
var div = document.getElementById("response");
// add paragraph to div
div.appendChild(paragraph);
// add unordered list to div
div.appendChild(unorderedList);
}

When working with these JavaScript objects from JSON web services you may have trouble figuring out how to reference the data. I just use a simple for loop to pop up an alert for each object property:

 for (prop in obj) {
    alert(prop);
}

After figuring out that JSON and callback functions were the solution to creating MSHelp2 mash ups, I was eager to expand my options by creating my own web service to supply data in the JSON format. Unfortunately, while it is easy enough to create a web service that returns JSON data feeds there is also the requirement to support callback functions and that has me stymied right now. One of the things I tried was Jayrock, JSON and JSON-RPC for the Microsoft .NET Framework. Jayrock makes it easy to create web services that return JSON data feeds using a generic web handler. Unfortunately, it appears to use the XMLHttpRequest object which gives me the familiar "Access is denied" JavaScript error. You can clearly see the XMLHttpRequest object being created in the JavaScript for the web service test page. It may be possible to use a callback function with Jayrock but the documentation does not provide any examples on how to do that.

I also tried to create a generic web handler that will use context.Response.ContentType = "application/json"; to return JSON data but I was still unable to implement the callback function. You may be able to do this quite simply by surrounding the JSON string with a function by appending the strings "wp_result("  and ")" but I cannot find any sample code to work out the exact syntax. If anyone can figure out how to implement the callback function, let me know.

You may be wondering why anyone should bother to call web services from within a help collection. I think it would be a very clever way to create mash ups in compiled help and project documentation. Many windows applications provide help in a compiled help files but this is static information. It is not kept up to date with new data unless you directly link to a live web page. Application help files could become applications in their own right using this technology. In any event, being able to create JSON web services that support callback functions is a useful trick so I intend to pursue this further. Yahoo! and Google supply such web services but it does not seem possible to do the same thing using ASP.NET.

UPDATE:
A tip from Hernan Garcia allowed me to complete my generic web handler. The trick was to change the ContentType to JavaScript so the object can be returned without requiring a callback function. To convert RSS feeds to JSON data, I used the code example that can be found on Piyush Shah's blog.

<%@ WebHandler Language="C#" Class="rss2json" %>

using System;
using System.Web;

public class rss2json : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        context.Response.ContentType = "application/javascript";
        string strFeed;
        strFeed = HttpContext.Current.Request.QueryString["feed"];
        // return an object
        strFeed = "var obj = eval (" + RssToJson.App_Code.RssDocument.Load(strFeed).ToJson() + ");";
        context.Response.Write(strFeed);
    }

    public bool IsReusable {
        get {
            return false;
        }
    }

} 

The web address for the feed is obtained from a query string value. A form in my custom help collection now allows me to paste in the web address for any RSS feed and display the post titles in an unordered list with a tooltip of the content. I can paste in additional feed addresses to continue to create lists just like on a mash up web site.

RSS to JSON

The JavaScript for the form could use a little improvement because you often need to click the button twice before the list will appear because I'm dynamically adding script blocks.

function ShowPosts() {
	//for (prop in obj.Channel) {
	//	alert(prop);
	//}
	// create paragraph 
	var header = document.createElement("h1");
	var textnode = document.createTextNode(obj.Channel.Description);
	header.appendChild(textnode);
	// create unordered list
	var unorderedList = document.createElement("ul");
	// create list items
	for (var i = 0; i < obj.Channel.Items.length; i++) {
	    var listitem = document.createElement("li");
	    var alink = document.createElement("a");
	    var itext = document.createTextNode(obj.Channel.Items[i].Title + "  (" + obj.Channel.Items[i].PubDate + ") ");
	    alink.appendChild(itext);
	    alink.setAttribute("href", obj.Channel.Items[i].Link);
		alink.setAttribute("title", stripTags(obj.Channel.Items[i].Description));
		listitem.appendChild(alink);
	    unorderedList.appendChild(listitem);
	}
	var div = document.getElementById("response");
	// add header to div
	div.appendChild(header);
	// add unordered list to div
	div.appendChild(unorderedList);
}
function stripTags(s) {
    return s.replace(/<\/?[^>]+>/gi, '');
}
function AddScript() {
	//alert(document.getElementById("feed").value);
	if (document.getElementById("feed").value != "") {
		jscript = document.createElement("script");
		jscript.setAttribute("type", "text/javascript");
		jscript.setAttribute("src", "http://localhost/rss2json/rss2json.ashx?feed="+document.getElementById("feed").value);
		document.getElementsByTagName('head')[0].appendChild(jscript);
	}
	else {
		alert("Enter the RSS feed web address first!");
		document.getElementById("feed").focus();
	}
}

15 Comments

Comments have been disabled for this content.