Quantcast
Channel: JSON – ASP Free
Viewing all articles
Browse latest Browse all 4

Ajax: Creating Native JavaScript Objects From SQL

$
0
0
Ajax is useful for fetching data to web pages without requiring that the pages reload; it speeds up the user experience of your web site. This article will show you how to build an ASP.NET web application in C# that takes advantage of Ajax’s dynamic ability.


A downloadable zip file is available for this article.

Overview

Ajax stands for Asynchronous JavaScript and XML. It’s an important technology because it allows you to fetch data after a web page has already been loaded, and modify the page according to the dynamic data it receives "behind the scenes." As the name implies, Ajax was initially intended for fetching XML. Once the XML data is received, the JavaScript code in the web page has to parse it and do something with it that your browser can understand. Usually, this means converting it into HTML, or modifying existing HTML form field values. But Ajax is not restricted to getting XML data from the servers.

JSON stands for JavaScript Object Notation. It’s a lightweight data-interchange format that the JavaScript language can understand natively, as an object. So, unlike XML, which has to be parsed, the JSON data can be converted directly into a real JavaScript object with a single call to the eval() function. You can then access the data the way you would any other JavaScript object, through member references. Data returned in JSON format is also generally smaller than the equivalent XML representation, which can save bandwidth. It is also "easy for humans to read and write…. [and] … easy for machines to parse and generate."

In this tutorial I will build a simple ASP.NET web application in C# that will provide a dynamic page (response.aspx) that can be passed SQL statements directly. The page will query against the Northwind database in Microsoft SQL Server, and return the data as a simple JSON object. An HTML document (ajaxtest.html) acts as the "client" to response.aspx, and demonstrates Ajax methods using JSON data.

{mospagebreak title=Setting up the .NET project}

To begin, I created, in Visual Studio .NET 2003, a new ASP.NET web application for C# at the root of my web server. I called the project "AJAJ" — Asynchronous JavaScript and JSON.

After the project was created, I deleted the default .aspx file, "WebForm1.aspx," and added a new Web Form item which I named "response.aspx." I also added a new HTML Page item, and called this file "ajaxtest.html."

Because we are not actually going to output any HTML, I deleted all but the first line of the response.aspx file:


Figure 1

We will be relying on the Response.Write() method to output the JSON data.

{mospagebreak title=The .NET server page}

I have designated response.aspx as the "server." This is because it was not intended to be directly consumed by the user; instead, it returns JSON formatted data to the front-end, ajaxtest.html, the "client." We will review the server side first.

The C# code for response.aspx resides in "response.aspx.cs." I begin modifying this file by including two classes that provide the SQL data object and convenient string array handling.

using System.Data.SqlClient;
using System.Collections.Specialized;

I then create a simple JSON object template using a multiline string. All of the text that begins with "__JTEMPL__" are tags that will be replaced when the request is processed against SQL data.

private string jsonTemplate = @"(
{
      ""rows"" : 
      [
__JTEMPL__COLUMNS__
      ], 

      ""totalRows"" : __JTEMPL__TOTALROWS__,
      ""error"" : __JTEMPL__ERROR__,
      ""status"" : __JTEMPL__STATUS__
}
)
";

Text in a JSON string that has the format string : value is a name/value pair. Anything enclosed in brackets ([]) refers to an array. So in the string jsonTemplate above, there is a variable (or member, to be more correct) called "rows" that will contain an array built from the table data returned by SQL. This array will in turn contain rows of name/value pairs that represent the column and data from the SQL table (see Figure 2). This will become more clear as we work through the rest of the code. totalRows is an object member that will contain the number of rows returned, and error and status are members that will contain any errors or status messages, as required. For a more detailed reference on the JSON format, please visit json.org.

Next, I define connStr, a connection string variable which provides access to my SQL server.

private string connStr =
"server=SQL-SERVER;database=Northwind;uid=sa;password=PASSWORD";

You will need to modify SQL-SERVER and PASSWORD in the sample to match your server setup before AJAJ will run properly. If you are using Microsoft SQL, you should have a Northwind sample database available, and the queries should run.

When an .aspx file is requested from the server, the action typically begins in the Page_Load() method.

private void Page_Load(object sender, System.EventArgs e)
{

   // Check parameters — we must be given an SQL command

   if ( Request.Params["sql"] == null )
   { 
     Response.Write( SetJsonError("No SQL statement given!"),
"null" );
   }
   else // If we have, do the work and output
   {
     Response.Write( SqlToJson() );
   }
}

The Page_Load() method is very simple. I merely check that the parameter "sql" has been passed to response.aspx, and if it hasn’t, I call and return SetJsonError() to the requester, setting the error value to the appropriate message. The SetJsonError() method sets values properly in jsonTemplate for the "error" and "status" members, and is used any time something goes wrong, or you need to inform the user of something. This way, no matter what parameters you give or don’t give to response.aspx, you will always get a properly formatted JSON object.

If the "sql" parameter has been passed, e.g. http://devweb/AJAJ/response.aspx?
sql=select%20CategoryName,Description%20from%20categories
, I then call SqlToJson() and return its output to the user. SqlToJson() is where the majority of the logic of AJAJ lives. I will describe it next. (Note: ellipses "…" in the code below indicate the method continues).

private string SqlToJson()
{

   // Instantiate the SQL connection

   object sqlConn = new SqlConnection(connStr);

   // Connect, trapping any errors into the JSON
   // object, and returning

   try
   {
     sqlConn.Open();
   }
     catch (Exception e)
   {
     return SetJsonError(
       e.ToString().Replace(Environment.NewLine, " ").Replace(
       """, "’").Replace("", ""), "null" );
   }

   // Since we have successfully opened the
   // connection, create a command object

   SqlCommand sqlCmd = new SqlCommand(Request.Params["sql"],
sqlConn);

First, I create a new SqlConnection object, and then I try to open the connection. If an exception occurs, I catch it and return it in the error portion of the JSON object, using, of course, SetJsonError(). I make sure to Replace() things like newline and double quotes (") in the exception message so that they are either removed or escaped in the JSON error string. If no exception occurs, I go ahead and create a new SqlCommand object using the connection string and the "sql" parameter.

Because SQL commands may come as query ("select") or non-query ("insert/update/delete") statements, and these are handled by different methods in the SqlCommand object, I check what type of command has been sent in the "sql" parameter.

   // Determine what kind of SQL command
   // we’ve been given, then act on it

   string [] sqlCmdType = Request.Params["sql"].ToString().Split(null,2);

   if (sqlCmdType[0].ToLower() == "select") // Data to be returned
   {

I do this by splitting the parameter on whitespace, saving the result in a string array. If the first element in the array is "select," I know that a query has been requested. If not, then I assume a non-query, and handle the error if the SQL command is merely ill-formed.

On a "select" statement, I run the command against the SQL server, saving the data into a SqlDataReader object. Then I iterate through this object and return the data as JSON formatted elements of the "rows" member. If an error occurs, I catch it and return.

   try
   {

     // Execute the SQL statement SqlDataReader

     sdr = sqlCmd.ExecuteReader();

     // Determine the column names, save in string array

     StringCollection scl = new StringCollection();
    
int _columncount = sdr.FieldCount;
     for (int i = 0; i < _columncount; i++)
     {
       scl.Add(sdr.GetName(i));
     }

     // Now, read SQL data and construct array of
     // values to add to the JSON template

     StringCollection jca = new StringCollection();

     // Populate array from given column names:

     while (sdr.Read())
     {
       string outStr = "";
       foreach (string col in scl)
       { 
         outStr += """ + col + "" : "" + sdr[col].ToString() +
"", ";
       }

       outStr = outStr.Substring(0, outStr.Length-2); // Remove
last comma/space 

       jca.Add( "{ " + outStr + " }" ); // Add to collection
     }

     sdr.Close();

     // Create new string array, and copy collection to it:

     string [] jtmplCols = new string [jca.Count];
     jca.CopyTo(jtmplCols,0);

     // Build output from JSON template, return

     string jtRet = jsonTemplate;

     jtRet = jtRet.Replace(
       "__JTEMPL__COLUMNS__", String.Join("," +
       Environment.NewLine, jtmplCols, 0, jtmplCols.Length));

     jtRet = jtRet.Replace("__JTEMPL__TOTALROWS__",
jca.Count.ToString() );

     jtRet = jtRet.Replace("__JTEMPL__ERROR__", "null"); // No
error occurred

     jtRet = jtRet.Replace("__JTEMPL__STATUS__", "null" );

     // Clean up
     sqlCmd.Dispose();
     sqlConn.Close();

     return jtRet;
   }
   catch (Exception e)
   {
    
return SetJsonError(
       e.ToString().Replace(Environment.NewLine, " ").Replace
(""", "’").Replace("", ""), "null" );
   }

First, in the code above, I create a SqlDataReader object, sdr, from the output of the ExecuteReader() method.

Because the "rows" member of the JSON object is an array of name/value pairs matching the column name to the column’s data for a particular row (Figure 2), I have to figure out what the column names are for any given SQL statement. I use a StringCollection object, scl, to dynamically build a collection of the column names. To do this I iterate over the index of the columns in sdr, and add their names to scl using sdr.GetName().

Once I’ve determined the columns, I iterate through the records in sdr and build the JSON text that will represent the array elements of the member "rows." Every iteration through the while(sdr.Read()) loop, a row of SQL data is returned. I take this data and format it as a JSON array element, like { "column1_name" : "column1_value", "column2_name" : "column2_value", … }. I add this value to another StringCollection object, jca. After I’ve iterated over all the records in sdr, I copy jca to a standard string array, jtmplCols. I do this so I can use String.Join() when I replace the template tags in jsonTemplate.

Next I create a local string named jtRet from jsonTemplate and replace all the template tags with the appropriate data. __JTEMPL__COLUMNS__ is replaced by the joined data in the string array jtmplCols, using comma plus newline as the delimiter. String.Join() is a convenient way of ensuring that there is no trailing delimiter after the last element. I use jca for one more thing, and that is to set the JSON member "totalRows" to the number of rows returned from SQL. At this point we’ve encountered no errors, so I set "error" and "status" to null, and return.

If the "select" statement above is sent to response.aspx, the following JSON data is returned and ready for consumption by the JavaScript code.


Figure 2 ("View Source" of response.aspx)

If a non-query SQL statement is passed via the "sql" parameter, I call the ExecuteNonQuery() method of the SqlCommand object. This returns a status value stating whether the command was successful. I use this value to set the "status" member of the JSON object before returning.

try
{

   string ret = SetJsonError(
      "null", sqlCmd.ExecuteNonQuery().ToString() );

   // Clean up
   sqlCmd.Dispose();
   sqlConn.Close();

   return ret;

}
catch(Exception e)
{
   return SetJsonError(e.ToString().Replace(Environment.NewLine, " ").Replace(""", "’").Replace("", ""), "null" );
}

Exceptions, of course, are caught and propagated to the JSON object. This gives you excellent information at the client level. For instance, if you pass response.aspx a bad SQL statement, the detailed exception that is generated in .NET would end up in the alert() output of ajaxtest.html.

{mospagebreak title=The Ajax client page}

Now that we’ve seen how the backend works, we are ready to investigate the JavaScript side of things. This is where the benefits of using JSON as a data format become very obvious.

ajaxtest.html is just an HTML file with JavaScript. All the logic on the page runs entirely on the client side, in the browser of the user. If you have successfully modified and gotten response.aspx to query against your Northwind database in the sample, you can load ajaxtest.html in your browser and see how it works.


Figure 3

On load, ajaxtest.html queries the "categories" table in the Northwind database, and populates a select list with the categories. Upon selection of one of these categories, it outputs the products of that category in a formatted table. It also uses a <div> to output the JSON directly for debugging.

The body of ajaxtest.html is as follows.

<body onload="loadCats()">

  <form id="theForm">

    <select name="selcats" onchange="outputProducts(this.value)">
    </select>

  </form>

  <div id="formattedOutput">
  </div>

  <strong>Debug:</strong>
  <div style="border:1px solid" id="debugOutput">

  <div>

</body>

The form "theForm" contains a simple, empty select list called "selcats." This is automatically populated by the JavaScript function "loadCats()," which is executed when the page loads. This executes because of the onload="loadCats()" part of the <body> tag.

// Load categories from Northwind databases
// (This occurs on page load)

function loadCats()
{

   http.open(‘get’,’response.aspx?sql=select CategoryID,
CategoryName from categories&rand=’ + genSeconds() );
   http.onreadystatechange = loadCatsCallback; // (See following
function)
   http.send(null);
   return false;

}

loadCats() uses an XMLHttpRequest object, http, which is created by the function createRequestObject() when the page is first loaded into the browser. createRequestObject() creates the appropriate XMLHttpRequest object for the browser being used (IE and Mozilla have different requirements) and returns it. Once http has been created, it can be used to send requests to response.aspx.

Above, I use http.open to call response.aspx with the SQL command asking for the CategoryID and CategoryName from the Northwind "categories" table. I assign the function loadCatsCallback() as the callback handler. When response.aspx returns the JSON data, this function will do something with it. Specifically, it will output it as <option> tags in "selcats."

function loadCatsCallback()
{
   if(http.readyState == 4)
   {

     // Create object from JSON notation. Note
     // use of ‘eval’

     var jsonObject = eval(http.responseText);

     // Check that there wasn’t an error

     if (jsonObject.error == null)
     {

       document.forms[‘theForm’].selcats.options[0] =
          new Option(”, ”); // The empty option

       // Loop through records, build <select> list
       for (var i=0; i < jsonObject.totalRows; i++)
       {
         document.forms[‘theForm’].selcats.options[i+1] =
            new Option( jsonObject.rows[i].CategoryName,
              jsonObject.rows[i].CategoryID );
       }

     }
     else
       alert("Error occurred: " + jsonObject.error);


     // Send information to debug <div>
     document.getElementById(‘debugOutput’).innerHTML =
"loadCatsCallback() JSON: " + http.responseText;

   }

} // loadCatsCallback()

In loadCatsCallback() above, I first check that the data has returned via readyState. Once it has, I create a variable called jsonObject using eval(). This is all there is to turning the data gotten from response.aspx into a usable JavaScript object. Convenient, yes? Now, I check the "error" member to see if it is null. If it is, then no errors have occurred, and we can process the object.

It’s a simple matter to iterate through the jsonObject.rows array — I create a for loop that counts from zero up to the value stored in jsonObject.totalRows, and obtain the values for the columns in each row by referring to their names. Notice that the value for the SQL column "CategoryID" is gotten by jsonObject.rows[i].CategoryID — as a variable. For each element in jsonObject.rows, I add a new Option to "selcats." I also output the straight JSON data to "debugOutput," using the innerHTML property of that <div> element.

Now, when you select a value in the "selcats" list, the function outputProducts() fires, passing the CategoryID of the selected option as a parameter. The ensuing code calls response.aspx with a SQL query to return products for that CategoryID, constructs a table from the query, and inserts it into the "formattedOutput" <div> using the innerHTML property. This code behaves in essentially the same way as that outlined above, so I will not cover it here.

Conclusion

JSON allows you to easily get at data in a native way with JavaScript, reducing the amount of code needed to process datasets. It executes fast, it’s easy to work with, and very convenient. It may not be the answer for every Ajax situation, but it’s certainly an important and powerful tool in the Ajax toolset.

The post Ajax: Creating Native JavaScript Objects From SQL appeared first on ASP Free.


Viewing all articles
Browse latest Browse all 4

Trending Articles