JSON And Cross Domain XML Requests
After finding a way to read RSS feeds in a custom help collection web page without cross domain restrictions, see previous blog post, I naturally wanted to do the same thing with XML feeds. You could probably figure that out for yourself given the RSS example but I hate it when I find sample code that does not quite do what I need. There were also some annoyances with my previous solution and I have some improvements.
The first step is to create a new generic handler to convert XML data to the JSON format:
<%@ WebHandler Language="C#" Class="xml2json" %>
using System;
using System.Web;
using System.Xml;
using System.Text; using System.Collections;
public class xml2json : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "text/javascript";
string strFeed;
string strJSON;
strFeed = HttpContext.Current.Request.QueryString["feed"];
XmlDocument xdoc = new XmlDocument();
xdoc.Load(strFeed);
// Convert XML to a JSON string
string JSON = XmlToJSON(xdoc);
// return an object
strJSON = "var obj = eval (" + JSON + ");";
context.Response.Write(strJSON);
}
public bool IsReusable {
get {
return false;
}
}
}
As for the code to actually do the data format conversion, XmlToJSON, I just used the first sample code I found on the Interet which was How to convert XML to JSON in ASP.NET C# but there may be better methods available. The generic handler can be passed any XML feed web address through the query string value. I used the National Weather Service for my tests because I'm familiar with their XML format. I created a simple form for inputing the weather station ID:
<form id="frm" action=""> Enter Weather Station ID: <input type="text" id="station" size="20" /> <br /><br /> <input type="button" value="Submit" id="btnSubmit" onclick="LoadJSON();" /> </form>
You also need a div where the response is going to appear. This div should appear above the JavaScript and the form should appear below the JavaScript:
<div id="response"></div>
In the JavaScript, I needed to make an improvement to deal with some latency in the response. I found I would need to click the submit button a few times before the JavaScript object would be available. So I added some code to test for the object and repeat the request if the object was undefined. This proved to be surprisingly difficult because it is tricky to test for an undefined JavaScript object without causing an error. That is something to add to my notes. I also needed to dynamically create a table so I learned a few more JavaScript DOM methods like insertRow and insertCell. I like it when these little research projects turn up some additional tips and tricks.
<script type="text/javascript"> // repeatedly calls AddScript until there is an object function LoadJSON() { if (document.getElementById("station").value != "") { AddScript(); while (typeof(obj) == "undefined") { AddScript(); } ShowWeather(); } else { alert("Enter the Weather Station ID first!"); document.getElementById("station").focus(); } } function AddScript() { jscript = document.createElement("script"); jscript.setAttribute("type", "text/javascript"); jscript.setAttribute("src", "http://localhost/rss2json/xml2json.ashx?feed=http://www.weather.gov/data/current_obs/" + document.getElementById("station").value + ".xml"); document.getElementsByTagName('head')[0].appendChild(jscript); } function ShowWeather() { //for (prop in obj.current_observation) { // alert(prop); //} // create table var table = document.createElement("table"); table.setAttribute("bgcolor","#eeeeee"); table.setAttribute("cellspacing","0"); table.setAttribute("cellpadding","1"); table.style.backgroundColor = "#eeeeee"; // create 1st row var tr = table.insertRow(-1) tr.setAttribute("bgcolor","black"); tr.style.backgroundColor = "black"; tr.style.color = "white"; var td = tr.insertCell(-1) td.setAttribute("colspan","2"); td.colSpan = 2; var loc = document.createTextNode(obj.current_observation.location); td.appendChild(loc); var br = document.createElement("br"); td.appendChild(br); var time = document.createTextNode(obj.current_observation.observation_time); td.appendChild(time); // create 2nd row var trWeather = table.insertRow(-1) var tdWeather = trWeather.insertCell(-1) var cellText = document.createTextNode("Weather:"); tdWeather.appendChild(cellText); var tdWeatherValue = trWeather.insertCell(-1) var cellText = document.createTextNode(obj.current_observation.weather); tdWeatherValue.appendChild(cellText); // create 3rd row var trTemperature = table.insertRow(-1) var tdTemperature = trTemperature.insertCell(-1) var cellText = document.createTextNode("Temperature:"); tdTemperature.appendChild(cellText); var tdTemperatureValue = trTemperature.insertCell(-1) var cellText = document.createTextNode(obj.current_observation.temperature_string); tdTemperatureValue.appendChild(cellText); // create 4th row var trHumidity = table.insertRow(-1) var tdHumidity = trHumidity.insertCell(-1) var cellText = document.createTextNode("Humidity:"); tdHumidity.appendChild(cellText); var tdHumidityValue = trHumidity.insertCell(-1) var cellText = document.createTextNode(obj.current_observation.relative_humidity + " %"); tdHumidityValue.appendChild(cellText); //create 5th row var trWind = table.insertRow(-1) var tdWind = trWind.insertCell(-1); var cellText = document.createTextNode("Wind:"); tdWind.appendChild(cellText); var tdWindValue = trWind.insertCell(-1); var cellText = document.createTextNode(obj.current_observation.wind_string); tdWindValue.appendChild(cellText); //create 6th row based on a condition if (obj.current_observation.windchill_string != null) { var trWindChill = table.insertRow(-1) var tdWindChill = trWindChill.insertCell(-1); var cellText = document.createTextNode("WindChill:"); tdWindChill.appendChild(cellText); var tdWindChillValue = trWindChill.insertCell(-1); var cellText = document.createTextNode(obj.current_observation.windchill_string); tdWindChillValue.appendChild(cellText); } else { var trHeatIndex = table.insertRow(-1) var tdHeatIndex = trHeatIndex.insertCell(-1); var cellText = document.createTextNode("HeatIndex:"); tdHeatIndex.appendChild(cellText); var tdHeatIndexValue = trHeatIndex.insertCell(-1); var cellText = document.createTextNode(obj.current_observation.heat_index_string); tdHeatIndexValue.appendChild(cellText); } // create table body var tbody = document.createElement("tbody"); table.appendChild(tbody); var div = document.getElementById("response"); // add table to div div.appendChild(table); } </script>
In an initial effort to solve the latency problem, I did some research into precompiling the generic handler. I'd never tried to precompile an ASP.NET 2.0 web site before and getting the correct command line tool syntax required some experimentation. This is another instance of the documentation failing to provide enough examples! When I copied the staging files into the web site I had to delete the App_Code directory and lost the source code for my generic handler although I'd already added that to my notes:
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727>aspnet_compiler -v /rss2json -p
"C:\Inetpub\wwwroot\rss2json" "C:\staging"
Utility to precompile an ASP.NET application
Copyright (C) Microsoft Corporation. All rights reserved.
This may actually be useful for me because I have several shipping rate web services that I need to document. Now I can build actual test scripts into my notes on these shipping rate services and eliminate the need to track down my test scripts. This project also taught me how to test for a JavaScript object, how to dynamically create a table, and how to precompile an ASP.NET 2.0 web application so it was a worthwhile exercise.