Home > Articles > Programming > ASP .NET

This chapter is from the book

This chapter is from the book

Implementing a Staged Page Load Process

We hinted earlier in this chapter that there are ways you can generate "real" status messages in the browser while executing a complex or lengthy operation on the server. Although the technique of simply flushing chunks of content back to the browser as the process runs does work, it's not particularly efficient in terms of connection usage or server loading.

Web servers are designed to receive a connection and resource request, generate the required response, and disconnect as quickly as possible to allow the next user to connect and make a resource request. Because it's likely that most complex operations will involve database access on the server, holding open a connection to the database while you flush chunks of content back to the client is probably not a good idea.

However, if you can break down the complex or lengthy process into separate individual stages, it is possible to provide useful "real" status feedback in the browser. In fact, it's reasonably easy to do this in Internet Explorer 5 and higher, by using the XMLHTTP object used in the previous example.

Flushing Intermediate Content to the Client

Of course, if the process has to access several different data sources to generate the resulting page, as is most likely the case with the MSN Expedia example mentioned earlier in this chapter, you can flush the individual chunks of "status" content to the browser in between opening each connection, extracting the data, and closing it again.

The Steps in Implementing a Staged Page Load Process

Figure 3.7 shows a flowchart of a staged process that is implemented as the next example in this chapter. The main page, named stagedloading.aspx, uses the XMLHTTP component to request a separate operation page, named stagedfetchpage.aspx, four times. Each request contains, in the query string, a customer ID that the user provides and a step value that indicates which stage of the process is currently being performed. The operation page uses these values to collect the appropriate row set from the Northwind database at each stage and add to a DataSet instance a table that is stored in the user's ASP.NET session.

In between requests, the main page can display progress and status information, or it can display any error messages returned by the operation page. When the process is complete in this example, the value returned (the total for all matching orders) is displayed—together with a button that allows the user to view the list of orders. This data is in the DataSet instance stored in the user's ASP.NET session, so it can be extracted and displayed without requiring another trip to the database.

Of course, you can easily tailor this example to display different data at any stage and provide links to access any of the tables in the DataSet instance. In fact, this process opens up a whole realm of opportunities for collecting data of all kinds and combining and then querying it afterward. Figure 3.8 shows a screenshot of the sample page while it is collecting details of orders for all customers whose ID starts with m and building up the DataSet instance.

Figure 3.7Figure 3.7 A flowchart of the steps in implementing a staged page load process.

Figure 3.8Figure 3.8 The staged processing and reporting sample page in action.

You'll learn about this page in more detail shortly, but first you need to see how you can pass status and other information back to the XMLHTTP object. Then you'll see how the operation page, which collects the data and stores it in the user's session, works. After that, you'll see how the main page calls this operation page and how it displays the status information and results.

Accessing Physically or Geographically Separated Data Sources

The set of steps used in this example could easily be performed in one pass. However, using separate stages demonstrates how you could in a more complex scenario access multiple different data sources that could be physically and geographically separated. These data sources might be Web services, XML documents, or other types of data sources—and not just relational databases. For instance, take the MSN Expedia example mentioned earlier: It's likely that the data sources being accessed would be hosted by different airlines, hotels, rental car companies, and so on.

Status Information in ASP.NET and the XMLHTTP Object

When a browser or any other client (such as XMLHTTP) requests an HTML page, the server returns an HTTP status header, followed by the page that was requested. If there is no error (that is, the page can be found and executed by the server), it returns the status header "200 OK".

However, even if the process of loading and executing the page succeeds, you can still control the status code that is returned by setting the Status, StatusCode, and/or StatusDescription properties of the current ASP.NET Response object. The values of these properties will be exposed by the status and statusText properties of the XMLHTTP object after it loads the page (see Table 3.2). You can find a full list of the standard HTTP status codes at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.

Table 3.2 The Equivalent Status-Related Properties of the ASP.NET Response and XMLHTTP Objects

ASP.NET Response Object Property

XMLHTTP Object Property

Description

Status

No direct equivalent

A combination of the status code and status description (for example, "200 OK" or "302 Object Moved")

StatusCode

status

The numeric part of the status information (for example, 200 or 302)

StatusDescription

statusText

The text or description part of the status information (for example, "OK" or "Object Moved")


By default, the server will automatically set the ASP.NET Status property to "200 OK" if there is no error or to the standard HTTP status code for any error that does occur (for example, "500 Internal Server Error" if there is an ASP.NET code execution error). However, if you trap ASP.NET errors in the code—for example, a failed database connection or a numeric calculation error—you must set the Status property (or the StatusCode and StatusDescription properties) if an error does occur.

The Staged Process Operation Page

The main page that the user sees makes repeated requests to the operation page (stagedfetchpage.aspx), passing the customer ID and the appropriate step number each time. Because it does this by using the XMLHTTP component, the operation page doesn't have to generate any HTML or output. All it has to do is indicate to the main page whether there was an error or whether this step of process succeeded.

However, not all the values you pass back to the XMLHTTP object in this example are strictly status messages; for example, the order value total that is displayed at the end of the process must be returned to the main page. So rather than use the StatusDescription property (statusText in XMLHTTP), you can write these messages directly into the page that is returned. The XMLHTTP object can retrieve this as the responseText property, as shown in the previous example.

The Page_Load Event Handler for the Staged Loading Example

Listing 3.7 shows the Page_Load event handler in the operation page, together with the page-level variable that holds a reference to the DataSet instance stored in the session. The values for the customer ID and the current step are collected from the query string each time the page loads.

Listing 3.7 The Page_Load Event Handler for the Staged Loading Example

Dim oDS As DataSet

Sub Page_Load()

 Dim sCustID As String = Request.QueryString("custID")
 Dim sStep As String = Request.QueryString("step")
 Dim sSelect As String

 ' force current thread to sleep for 3 seconds
 ' to simulate complex code execution
 Thread.Sleep(3000)

 Select Case sStep
  Case "1"
   oDS = New DataSet()
   sSelect = "SELECT CustomerID, CompanyName, City, " _
    & "Country, Phone FROM Customers " _
    & "WHERE CustomerID LIKE @CustomerID"
   AddTable("Customers", sCustID, sSelect)
  Case "2"
   oDS = CType(Session("thedata"), DataSet)
   sSelect = "SELECT OrderID, OrderDate FROM Orders " _
    & "WHERE CustomerID LIKE @CustomerID"
   AddTable("Orders", sCustID, sSelect)
  Case "3"
   oDS = CType(Session("thedata"), DataSet)
   sSelect = "SELECT [Order Details].OrderID, " _
    & "Products.ProductID, Products.ProductName, " _
    & "[Order Details].Quantity, [Order Details].UnitPrice " _
    & "FROM [Order Details] JOIN Products " _
    & "ON [Order Details].ProductID = Products.ProductID " _
    & "WHERE [Order Details].OrderID IN " _
    & " (SELECT OrderID FROM Orders " _
    & " WHERE CustomerID LIKE @CustomerID)"
   AddTable("OrderDetails", sCustID, sSelect)
  Case "4"
   oDS = CType(Session("thedata"), DataSet)
   CalculateTotal()
  Case Else
   Response.Status = "500 Internal Server Error"
   Response.Write("Error: Invalid Query String Parameter")
 End Select

End Sub

Next, to simulate a long process, you force the current thread to sleep for 3 seconds (as you did in the "please wait" example) before using the step value from the query string to decide which action the page will carry out. The first three stages of the operation must create and execute a database query to extract the appropriate set of rows and then add these to the DataSet instance in the user's session. The AddTable routine, which you'll see shortly, achieves this. Obviously, you have to a create new DataSet instance at Stage 1, but the remaining stages can extract this DataSet instance from the user's session.

At Stage 4 in this example, the operation page has to calculate the order total and return it to the main page, using the routine CalculateTotal (which you'll see shortly). Any value greater than 4 for the step parameter is treated as an error, and the page returns the server-side execution error "500 Internal Server Error". A more detailed error message is also sent back as the content of the returned page.

Accessing the Customer ID Value

The value of the customer ID entered into the text box cannot be extracted directly as the Text property of the ASP.NET TextBox control when this page is executed. The page is loaded with the "GET" method by the XMLHTTP object, with the customer ID appended to the query string, so it must be collected from there each time.

What Happens if Cookies Are Disabled?

The sample page will fail to work properly if the user has cookies disabled in his or her browser because ASP.NET will not be able to maintain a user session. One solution would be to enable cookieless sessions by adding the element <sessionState cookieless="true" /> to the <system.web> section of the web.config file for the application. In this case, you must also modify the src attribute of the non–server control <img> elements to specify the full path to the images because the inclusion of the session key in the page URL breaks the links to images that are specified only as relative paths from the URL of the page that hosts them.

Adding Tables to the DataSet Instance

Adding a table to the DataSet instance you extract from the user's session is simple, and the code in Listing 3.8 demonstrates the traditional techniques you use. Notice that, in this code, you check whether you actually managed to find a DataSet instance in the session, and you return an error status and message if not. After adding the table, you push the updated DataSet instance back into the session. If there is an error while extracting the rows, a suitable error status and message are returned to the user instead.

Listing 3.8 The AddTable Routine for the Staged Loading Example

Sub AddTable(sTableName As String, sCustID As String, _
       sSelect As String)

 If oDS Is Nothing Then

  Response.Status = "500 Internal Server Error"
  Response.Write("Error: Cannot access DataSet in session")

 Else

  Dim sConnect As String = ConfigurationSettings.AppSettings( _
                "NorthwindSqlClientConnectString")
  Dim oConnect As New SqlConnection(sConnect)
  Dim oDA As New SqlDataAdapter(sSelect, oConnect)
  oDA.SelectCommand.Parameters.Add("@CustomerID", sCustID & "%")

  Try

   ' fill table in DataSet and put back into session
   oDA.Fill(oDS, sTableName)
   Session("thedata") = oDS
   Response.Status = "200 OK"
   Response.Write("OK")

  Catch oErr As Exception

   Response.Status = "500 Internal Server Error"
   Response.Write("Error: " & oErr.Message)

  End Try

 End If

End Sub

Calculating the Total Value of the Orders

The final section of the operation page in the staged loading example is shown in Listing 3.9. This simply references the OrderDetails table in the DataSet instance and sums the values in each row by multiplying the quantity by the unit price. The result is written back to the response as a fixed-point number with two decimal places.

Listing 3.9 The CalculateTotal Routine for the Staged Loading Example

Sub CalculateTotal()

 Dim dTotal As Decimal = 0

 Try

  For Each oRow As DataRow In oDS.Tables("OrderDetails").Rows
   dTotal += (oRow("Quantity") * oRow("UnitPrice"))
  Next

  Response.Status = "200 OK"
  Response.Write(dTotal.ToString("F2"))

 Catch oErr As Exception

  Response.Status = "500 Internal Server Error"
  Response.Write("Error: " & oErr.Message)

 End Try

End Sub

The Staged Process Main Page in the Staged Loading Example

Now that you have seen how the operation page performs the updates to the DataSet instance and returns status and information messages, you can now look at the main page that calls this operation page at each stage of the overall process. Listing 3.10 shows the HTML content of the main page. You can see that there is an ASP.NET TextBox control for the user to enter the full or partial customer ID and an <input> element that creates the submit button captioned Calculate.

Listing 3.10 The HTML Declarations for the Main Page in the Staged Loading Example

<form runat="server">

 <!----- form for selecting customer ----->
 <asp:Label id="lblEnter" runat="server" 
       Text="Enter Customer ID:" />
 <asp:Textbox id="txtCustomer" runat="server" /><br />
 <input id="btnGo" type="submit" value="Calculate"
     onclick="return getResults();" runat="server"/>

 <!----- "please wait" display ----->
 <table border="0">
 <tr>
  <td><img id="img1" src="../images/False.gif" width="12" 
       height="12" hspace="5" /></td>
  <td><span id="spn1">Loading Customer Data</span></td>
 </tr><tr>
  <td><img id="img2" src="../images/False.gif" width="12" 
       height="12" hspace="5" /></td>
  <td><span id="spn2">Loading Orders Data</span></td>
 </tr><tr>
  <td><img id="img3" src="../images/False.gif" width="12" 
       height="12" hspace="5" /></td>
  <td><span id="spn3">Loading Order Details</span></td>
 </tr><tr>
  <td><img id="img4" src="../images/False.gif" width="12" 
       height="12" hspace="5" /></td>
  <td><span id="spn4">Calculating Total</span></td>
 </tr>
 </table>

 <!----- section for displaying total ----->
 <div id="divResult">
  <b><span id="spnResult"></span></b><p />
 </div>

 <!----- section for displaying orders ----->
 <div id="divOrderList">
  <asp:Button id="btnOrders" style="visibility:hidden"
     Text="Show Orders" OnClick="ShowOrders" runat="server" />
  <asp:DataGrid id="dgrOrders" EnableViewState="False" 
         runat="server" /><p />
 </div>

</form>

<img id="imgTrue" style="visibility:hidden" 
   src="../images/True.gif" />
<img id="imgThis" style="visibility:hidden" 
   src="../images/This.gif" />

You use the HTML <input> element here because this is easier to connect to a client-side event handler than the ASP.NET Button element. (You don't have to add the onclick attribute on the server via the Attributes collection.) You always return false from the event handler that is attached to this button because you must prevent it from submitting the page to the server.

Declaring the Button as a Server Control

You could omit the runat="server" attribute from the button. This would mean that the <input> element would not be a server control. However, you want to be able to hide the button if the browser is not Internet Explorer 5 or higher, and, because you perform this check on the server side when the page loads (as you'll see shortly), you need to be able to reference it in the server-side code.

You could also use the HTML <button> element instead of the <input> element. The <button> element is not supported in all browsers, but because this page will work only in Internet Explorer (where it is supported), this would not be an issue.

The HTML table that follows the text box and button contains an <img> element and a <span> element for each stage of the process. The client-side code that executes the operation page will update the src attribute of the <img> element to change the image that is displayed and the font-weight style selector of the text as each stage takes place.

The other two sections of the page are a <div> section, where any error messages and the final order total will be displayed as each stage of the process executes, and another <div> section, where the list of orders is displayed if the user clicks the Show Orders button. You'll learn about this aspect of the sample page after you see how it performs the initial four stages of calculating the order total.

Finally, right at the end of the page are two more <img> elements that are hidden from view with the visibility:hidden style selector. You use these to preload the images for the list of operation stages. You display the image named This.gif (a right-pointing arrow) for each stage as it starts and then replace it with the image True.gif (a large check mark) if it completes successfully. You can see these two images in Figure 3.8.

Displaying the Current Operation Progress in the Staged Loading Example

Listing 3.11 shows the two client-side JavaScript functions you use to manipulate the progress indicators in the page. As each stage of the process is started, you make a call to the setCurrent function. As each stage completes, you call the setCompleted function. In both cases, you supply the stage number (a value from 1 to 4 in this example) as the single parameter.

Listing 3.11 The Client-Side Routines to Display Operation Progress in the Staged Loading Example

function setCurrent(iStep) {
 // get reference to image and change to "arrow"
 // using image pre-loaded in hidden <img> element
 var oImg = document.getElementById('imgThis');
 var oElem = document.getElementById('img' + iStep.toString());
 oElem.src = oImg.src;
 // get reference to span and change text to bold
 oElem = document.getElementById('spn' + iStep.toString());
 oElem.style.fontWeight = 'bold';
}

function setCompleted(iStep) {
 // get reference to image and change to "tick"
 // using image pre-loaded in hidden <img> element
 var oImg = document.getElementById('imgTrue');
 var oElem = document.getElementById('img' + iStep.toString());
 oElem.src = oImg.src;
 // get reference to span and change text back to normal
 oElem = document.getElementById('spn' + iStep.toString());
 oElem.style.fontWeight = '';
}

The code in the setCurrent and setCompleted functions is very similar. It starts by getting a reference to the preloaded and hidden <img> element that contains either the arrow image (This.gif) or the check mark image (True.gif).

The <img> and <span> elements that indicate the four process stages shown in the page have values for their id attributes that indicate which stages they apply to. For example, the first stage uses the id attributes "img1" and "spn1", respectively, for the <img> and <span> elements. So the code can get references to the correct elements by using the step number passed to it as a parameter.

With these references, it's then just a matter of updating the src property of the <img> element to display the appropriate image and setting the style.fontWeight property of the <span> element.

Executing the Operation Page with XMLHTTP

Listing 3.12 shows the code that executes the operation page discussed earlier in this chapter. Three page-level variables are declared to hold references to items that will be accessed from separate functions: the <span> element, where the status and any error messages are displayed, the XMLHTTP object, and the customer ID that the user entered.

Listing 3.12 The Client-Side Routines to Execute the Operation Page

var oResult;
var oHTTP;
var sCustID;

function getResults() {
 // get reference to "result" label and texbox value
 oResult = document.getElementById('spnResult');
 var oTextbox = document.getElementById('txtCustomer');
 sCustID = oTextbox.value;
 if (! sCustID == '') {
  // hide DataGrid control
  var oElem = document.getElementById('dgrOrders');
  if (oElem != null) oElem.style.visibility = 'hidden';
  // get Customers data
  fetchData(1)
 }
 else
  oResult.innerText = 'No customer ID specified';
 // return false to prevent button from submitting form
 return false;
}

function fetchData(iStep) {
 // create instance of a new XMLHTTP object because we
 // can't change readystate handler on existing instance
 oHTTP = new ActiveXObject('Microsoft.XMLHTTP');
 if (oHTTP != null) {
  // update status display and build data page URL
  setCurrent(iStep);
  var sURL = 'stagedfetchpage.aspx?custid=' + sCustID
       + '&step=' + iStep.toString();
  // set correct handler for XMLHTTP instance
  switch (iStep) {
   case 1: {
    oHTTP.onreadystatechange = gotCustomers;
    break;
    }
   case 2: {
    oHTTP.onreadystatechange = gotOrders;
    break;
    }
   case 3: {
    oHTTP.onreadystatechange = gotDetails;
    break;
    }
   case 4: {
    oHTTP.onreadystatechange = gotTotal;
    }
  }
  // open HTTP connection and send async request
  oHTTP.open('GET', sURL, true);
  oHTTP.send()
 }
 else
  oResult.innerText = 'Cannot create XMLHTTP object';
}

Next comes the main getResults function, which is executed when the Calculate button is clicked. It collects a reference to the <span> element that will hold the results, along with the customer ID that the user entered into the text box on the page. If there is a value here, it hides the DataGrid control that could still be displaying the list of orders from a previous query, and then it calls the fetchData function with the parameter set to 1 to perform Stage 1 of the process. If there is no customer ID, it just displays an error message instead.

The fetchData function (also shown in Listing 3.12) will be called at each stage of the process, starting—as you've just seen—with Stage 1. This function's task is to create an instance of the XMLHTTP object and execute the operation page with the correct combination of values in the query string. It first checks that an instance of XMLHTTP was in fact created, and then it calls the setCurrent function shown in Listing 3.11 to update the status display in the page. Then it creates the appropriate URL and query string for this stage of the process.

However, recall that you have to access the operation page asynchronously to allow the main page to update the status information, so you must specify a client-side event handler for the readystatechange event of the XMLHTTP object. The page contains four event handlers, and you select the appropriate one by using a switch statement before opening the HTTP connection and calling the send method of the XMLHTTP object to execute the operation page.

Handling the XMLHTTP readystatechange Events

Listing 3.13 shows the four event handlers that are declared in the switch statement in Listing 3.12. They are all very similar, and by looking at the first of them, gotCustomers, you can see that they do nothing until the loading of the operation page is complete (when the readystate property is 4). Then, if the status code returned from the operation page is 200 ("OK"), they call the setCompleted function shown in Listing 3.11 to indicate that this stage completed successfully. If any other status code is returned, the code displays the value of the responseText property (the content of the page returned, which will be the error details) in the page.

Listing 3.13 The Event Handlers for the XMLHTTP readystatechange Event

function gotCustomers() {
 // see if loading is complete
 if (oHTTP.readyState == 4) {
  // check if there was an error
  if (oHTTP.status == 200) {
   // update status display and fetch next set of results
   setCompleted(1);
   fetchData(2);
  }
  else
   oResult.innerText = oHTTP.responseText;
 }
}

function gotOrders() {
 // see if loading is complete
 if (oHTTP.readyState == 4) {
  // check if there was an error
  if (oHTTP.status == 200) {
   // update status display and fetch next set of results
   setCompleted(2);
   fetchData(3);
  }
  else
   oResult.innerText = oHTTP.responseText;
 }
}

function gotDetails() {
 // see if loading is complete
 if (oHTTP.readyState == 4) {
  // check if there was an error
  if (oHTTP.status == 200) {
   // update status display and fetch next set of results
   setCompleted(3);
   fetchData(4);
  }
  else
   oResult.innerText = oHTTP.responseText;
 }
}

function gotTotal() {
 // see if loading is complete
 if (oHTTP.readyState == 4) {
  // check if there was an error
  if (oHTTP.status == 200) {
   // update status display
   setCompleted(4);
   // display result in page and show Orders button
   oResult.innerText = 'Total value of all orders $ '
            + oHTTP.responseText;
   var oElem = document.getElementById('btnOrders');
   oElem.style.visibility = 'visible';
  }
  else
   oResult.innerText = oHTTP.responseText;
 }
}

As each stage completes, the code must initiate the next stage. In the first three event handlers (shown in Listing 3.13), this just involves calling the fetchData function (shown in Listing 3.12) again—but with the next stage number as the parameter. The instance of the XMLHTTP object that is created will then have the event handler for the next stage attached to the readystatechange event.

At Stage 4, when the gotTotal function is called after the operation page has successfully calculated and returned the total value of matching orders, the responseText property will return the total as a string. The function displays this value in the page and then changes the visibility style selector of the Show Orders button to make it visible. However, if there is an error, the error message is displayed instead.

Figure 3.9 shows the sample page after the four steps have completed successfully. You can see that the order total is displayed and the Show Orders button is now visible as well.

Figure 3.9Figure 3.9 The sample page, after successfully processing all the stages.

Fetching and Displaying a List of Orders

After the four stages of the process in the staged loading example have completed successfully, the user's session contains a DataSet instance that is fully populated with lists of matching customers, orders, and order details rows from the database. This means that you can easily display some or all of the results of the four-stage process (as well as the total already displayed in the page) by querying this DataSet instance—without having to hit the database again.

The Show Orders button (refer to Figure 3.9), which appears only after all four stages of the operation are complete, runs a server-side routine that extracts a list of order lines from the DataSet instance and displays them in the DataGrid control included in the HTML declarations of the page. Figure 3.10 shows the result.

Figure 3.10Figure 3.10 The sample page, displaying the list of orders from the cached DataSet instance.

Why Do the Check Mark Images Disappear?

Notice that the check mark images disappear from the page following the postback that populates the DataSet instance. Remember that unlike changes made in server-side ASP.NET code, any changes made to the page displayed in the browser using client-side script are not persisted across postbacks.

The Server-Side Code in the Staged Process Main Page

Most of the action in the main page in the staged loading example is the result of the client-side script examined in the previous section. However, two tasks require server-side code. Because the page will work only in Internet Explorer 5 and higher, you really should make some attempt to test the browser type and display an error message in other browsers. Second, you need to handle click events for the Show Orders button and populate the DataGrid control that displays the list of order lines.

Listing 3.14 shows the complete server-side code for the main page. In the Page_Load event, you can access the BrowserCapabilities object that is exposed by the Request.Browser property and test the browser name and version. If the browser is not Internet Explorer 5 or higher, you display an error message and hide the text box and Calculate button so that the page cannot be used.

Listing 3.14 The Server-Side Page_Load and ShowOrders Event Handlers

Sub Page_Load()
 ' check that the browser is IE 5 or higher
 If Request.Browser.Browser <> "IE" _
 Or Request.Browser.MajorVersion < 5 Then
  ' display message and hide input controls
  lblEnter.Text = "Sorry, this page requires Internet Explorer 5 or higher"
  txtCustomer.Visible = False
  btnGo.Visible = False
 End If
End Sub

Sub ShowOrders(sender As Object, args As EventArgs)

 ' bind DataGrid to contents of DataSet in user's Session
 dgrOrders.DataSource = CType(Session("thedata"), DataSet)
 dgrOrders.Datamember = "OrderDetails"
 dgrOrders.DataBind()

End Sub

When the Show Orders button is clicked (after the four stages of the process in the sample page are complete), the routine named ShowOrders is executed. This simply accesses the DataSet instance stored in the user's session, binds the OrderDetails table to the DataGrid control, and calls the DataBind method.

Catching and Displaying Errors from the Operation Page

The code shown in the preceding sections is designed to cope with any errors that might occur in the operation page, which does the real work of querying the database and building up the DataSet instance that contains all the results. As with any database operation, there is a possibility that something will go wrong—from a failed connection to changed permissions within the tables, changed column names, or even network failure if the database server is remote from the Web server.

Making the Staged Process Work in Other Browsers

The staged loading example absolutely requires that the MSXML parser be available on the client and so it works only in Internet Explorer 5 and higher. However, it could be implemented in other browsers (and different types of clients), using other suitable client-side software components. There are Java applets available that could be used in other browsers, or you could create your own Java applet or ActiveX controls. The main issue will be persuading the user to install these. Although this solution would be fine on an intranet where you can install the code on each machine and keep control, users out there on the Internet might be less keen to download unknown components and allow them to run.

As you've seen, the operation page returns one of the standard HTTP status codes each time, and it writes output into the page it generates. This content consists of just the text "OK" for the first three stages (where the DataSet instance is being created), but this text is not displayed in the main page. However, if there is an error within the operation page, the XMLHTTP object detects it because the status code is not 200, and it displays the contents of the returned page.

As an example, if you change the SQL statement used for Stage 3 (extracting the order details) so that it references a non-existent column in the database, the Try...Catch construct in the operation page code (refer to Listing 3.8) catches the error. It returns the status code "500 Internal Server Error" and the text "Error:", followed by the error message (as returned by ASP.NET when the data access operation failed) as the content of the page. The client-side code then displays the returned page content, as shown in Figure 3.11.

Figure 3.11Figure 3.11 The sample page, reporting a data access error.

Although it's taken a while to examine the code used in this example, you can see that it is not really very complicated. It allows you to create and manage staged processes that provide accurate feedback to users and that can manage errors and display useful status information.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020