All Work and No Play...
As of December 2011, this topic has been archived. As a result, it is no longer actively maintained. For more information, see Archived Content. For information, recommendations, and guidance regarding the current version of Internet Explorer, see Internet Explorer Developer Center.
By Jay Allen, Mark Davis, Heidi Housten, and Tosh Meston
Microsoft Corporation
November 12, 2002
The year is winding down, the air is getting cooler, the leaves are falling off the trees, and the hearts and minds of the Web Team turn towards an important matter—the debut of Xbox Live™! The Web Team's been busy running Ethernet over to the TV, fluffing up the couch cushions, and picking out our Gamertags.
But before we get to fraggin' time, we've answered and packaged up another set of questions e-mailed in from devoted readers. This month we've got:
- Tips for dealing with long running tasks in your Web applications.
- A behavior for making table bodies scrollable.
- A method to clear the authentication cache through script.
- And more great short-answer solutions.
Contents
Finding More Time on the Web—Designing Web applications around long running tasks
Out for a Scroll—An HTC for making table bodies scrollable
Finding More Time On the Web
Dear Web Team:
I have an ASP page that calls an ActiveX DLL from an intranet site. The DLL performs a lengthy task of creating Microsoft Word and Excel files. Unfortunately, while the task is in progress, Internet Explorer 5.0 displays an error page.
Rajesh
The Web Team replies:
At its simplest, a Web browser requests a page from a Web server and, some moments later, the Web server dishes up a response in the form of an HTML Web page (or some other format). Web servers are designed to handle thousands of such synchronous transactions each minute. Most of the time a Web server returns the Web page we requested, but when something goes wrong, we get an error page that displays an HTTP status code.
Internet users are familiar with many of the status codes returned by Web servers, such as the 404 error when a page is not found. The 400 range of status codes are reported by a Web server to indicate that there is an error in the browser request that prevents the response being fulfilled. The 500 range of status codes are reported by a Web server to indicate a server error. Most likely, your ASP page request is returning a 500 error (Internal Server Error). ASP provides more detailed information within the Technical Information section of the status page and one possible reason is that your script has taken too long to complete.
So, this is all interesting, but how does it relate to your problem? Well, the first thing is that now you understand the synchronous nature of the Web, and you can appreciate that a well-designed Web application will limit the amount of resources and time in which it processes Web requests. If you take too long to process a Web request, the Web server has less time to process other Web requests, the server may timeout, and the user will not know how their request is doing. Knowing this information, you can do a number of things to solve this problem.
The first thing you can do is inform ASP that you would like to give your script more time to run by setting the Server.ScriptTimeout property. This is is a heavy-handed approach (the default is 90 seconds), so you'll probably want to inform the user that this is a lengthy operation before they submit the request. The limited attention span of users is a more important consideration than any browser or server timeout! A simple way to inform the user is to return a Web page that displays a message and immediately submits a request to the ASP page that is going to perform the lengthy operation.
<HTML>
<HEAD>
<SCRIPT>
function init()
{
parent.location.href = "/timeout_work.asp";
}
</SCRIPT>
</HEAD>
<BODY ONLOAD="init()">
Please wait while your files are generated...
</BODY>
</HTML>
An even better solution is to delegate the task to another process, for example, using Message Queuing (MSMQ). Microsoft .NET encapsulates this functionality in the System.Messaging namespace. On receiving a request, the Web server submits a message to the queue, where another application performs the processing. The Web server returns a Web page to the user that contains a progress report. The page refreshes periodically to check progress at the Web server and updates the progress accordingly. The discussion of message queuing and checking progress is outside the scope of this article, but here is an example Web page that the Web server can return:
<HTML> <HEAD> <META HTTP-EQUIV="REFRESH" CONTENT="10;URL=refresh.htm?id=[session-id]"> </HEAD> <BODY> Please wait while we generate your files...<BR> Estimated time to completion ... 5 seconds </BODY> </HTML>
If you can break the task into smaller operations, then you could have the server return a Web page when each chunk is completed. Each Web page would provide the user with feedback about the progress and then submit a request for the next piece of work.
Finally, you could consider reducing the processing time of the task. If your ActiveX® DLLs are automating Word and Excel to generate the files, then perhaps another format could be used. It's important to note that Microsoft Office products are designed to run on a client, not on a Web server. Any software that runs on a Web server would ideally be specifically designed to function and scale well in a server environment, and this is an important design consideration when implementing your server components. If you are generating the files to be viewed by the user, you may want to consider returning HTML to the user. You can set the MIME header type to enable the HTML to be opened by another application, such as Excel. See Giving Your Name and Other Properties for more details.
Out for a Scroll
Dear Web Team:
I'd like to have an HTML table element on my Web page where the first TR element (or maybe <THEAD>) is static, while the rest of the TR elements (or <TBODY>) can scroll? So far, I've tried creating a separate table element for the header and then wrapping a div element around the "body" table and added "overflow: auto" to the style attribute. I'm developing for a client who is using Internet Explorer 5.0 or higher exclusively, so there's no need for cross-browser compatibility.
If you come up with a solution, how would I combine it with the ASP.NET DataGrid?
Thanks,
Christopher Perrins
The Web Team replies:
If the Web Team had a dollar for every time we've seen an e-mail from a developer asking for this feature we wouldn't be pining for the good old days when the NASDAQ was sky high. Well, it's high time we addressed this request and satisfied our loyal readers. Naturally, we thought the best way to do this was to come up with an HTML Behavior that we could attach to any table we want, even ones emitted by the ASP.NET DataGrid. Our control, ScrollTable.htc, is yours to customize or improve. We'll explain how it works.
First, the scrolltable.htc is attached to our table element by specifying the behavior attribute in the CSS class used on the element.
behavior:url(scrolltable.htc);
When the ondocumentready event fires, our behavior creates two table elements by calling cloneNode(false) on our table. We clone the table without child elements rather than creating new tables so that we preserve attributes, such as cellSpacing and style, instead of manually having to add each attribute to our two new tables. The two table elements we create are the header containing the first row of the table in the test page and the body containing all of the other rows. The body table is inserted into a container DIV with the CSS overflow attribute set to auto, which allows scrolling if necessary. The table in the test page is then modified to contain only two rows. The first row contains a TD and the header table, and the second row contains a TD with the container DIV. Next, we store the offsetWidths of all the cells in the first row of the table in an array, rgWidths, so that we can set the cells in the header and body tables and they remain in sync. Then we add the header row to the tblHeader element and the body rows to the tblBody element. We then insert the tblBody into the container DIV. After that, we remove all the rows from our table and insert two new rows—one for the tblHeader and one for tblBody. Once all the rows are added to the body and header tables, we set the width of the columns in the two tables to the width values stored in our array. Finally, we set some properties on the header and body to make them look right.
In TestPage.htm, one can see that we have added three expando properties to our TABLE. The first attribute, bodyHeight, is used to set the height of the body. If this property is not set, the body will not scroll. We also have headerCSS and bodyCSS, which simply allow you to specify separate CSS classes for the header and body tables.
Here are the sources:
ScrollTable.htc
<PUBLIC:ATTACH event="ondocumentready" HANDLER="onDocumentReady" />
<SCRIPT LANGUAGE="JScript">
function onDocumentReady()
{
// Create elements
var tblHeader = this.cloneNode(false);
var tblBody = this.cloneNode(false);
var divCntr = document.createElement("DIV");
// Get column widths
var rgWidths = new Array();
for (var i = 0; i < this.rows[0].cells.length; i++)
{
rgWidths[i] = this.rows[0].cells[i].offsetWidth;
}
// Add header row
var tbdyHeader = document.createElement("TBODY");
tblHeader.appendChild(tbdyHeader);
tbdyHeader.appendChild(this.rows[0].cloneNode(true));
// Add body rows
var tbdyBody = document.createElement("TBODY");
tblBody.appendChild(tbdyBody);
for (var i = 1; i < this.rows.length; i++)
{
var oRow = this.rows[i].cloneNode(true);
tbdyBody.appendChild(oRow);
}
// Set up body container
divCntr.style.overflow = "auto";
if (this.bodyHeight) divCntr.style.height = this.bodyHeight;
divCntr.appendChild(tblBody);
// Change existing table
for (var i = this.rows.length; i > 0; i--)
{
this.rows[i-1].removeNode(true);
}
var tr1 = this.insertRow();
var td1 = tr1.insertCell();
var tr2 = this.insertRow();
var td2 = tr2.insertCell();
td1.appendChild(tblHeader);
td2.appendChild(divCntr);
// Set column widths of all but the last column
for (var i = 0; i < rgWidths.length - 1; i++)
{
tblHeader.rows[0].cells[i].width = rgWidths[i];
tblBody.rows[0].cells[i].width = rgWidths[i];
}
tblHeader.style.fontSize = "100%";
tblHeader.width = "100%";
tblHeader.style.tableLayout = "fixed";
tblHeader.className = this.headerCSS ? this.headerCSS : "";
tblHeader.border = 0;
tblBody.style.fontSize = "100%";
tblBody.width = "100%";
tblBody.style.tableLayout = "fixed";
tblBody.className = this.bodyCSS ? this.bodyCSS : "";
tblBody.border = 0;
this.cellSpacing = 0;
this.cellPadding = 0;
}
</SCRIPT>
TestPage.htm
<HTML>
<HEAD>
<STYLE>
.tblMain
{
behavior:url(scrolltable.htc);
background-color: highlight;
border: 1px solid darkblue;
font-family: Verdana;
font-size: .8em;
}
.tblHeader
{
color: highlighttext;
}
.tblBody
{
background-color: #EEEEEE;
color: darkblue;
}
</STYLE>
</HEAD>
<BODY>
<TABLE bodyHeight="100" bodyCSS="tblBody" headerCSS="tblHeader"
CLASS="tblMain" WIDTH="75%" CELLSPACING=5 CELLPADDING=0>
<TR>
<TD NOWRAP>Header A</TD>
<TD NOWRAP>Header B</TD>
<TD NOWRAP>Header C</TD>
</TR>
<TR>
<TD>Aaaaaaaaaaaaaaaaaaa</TD><TD>B</TD><TD>Ccccccccccccccccccc</TD>
</TR>
<TR>
<TD>A</TD><TD>Bbbbbbb</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
<TR>
<TD>A</TD><TD>B</TD><TD>C</TD>
</TR>
</TABLE>
</BODY>
</HTML>
Web Team in Short
Your Credentials, Please
Q: Jerry B. writes, "After the user has validated and processed his request, I now want to invalidate him. Assuming this machine is in an open environment where anyone could walk up and us it, I want to throw a new challenge each time a user accesses a particular module on the Web."
A: This is a frequently requested feature of the Internet Explorer team and the good people over there have given us a way to do it in Internet Explorer 6.0 SP1. All you need to do is call the execCommand method on the document, passing in ClearAuthenticationCache as the command parameter, like this:
document.execCommand("ClearAuthenticationCache");
This command flushes all credentials in the cache, such that if the user requests a resource that needs authentication, the prompt for authentication occurs again.
A Shared Sense of COM
Q: Shailen Bhatt asks, "I want to use a COM component I developed in my ASP application. The thing is, I want to use the same object of the component in all the ASP pages. How can I use the same object in all the ASP pages?"
A: Well Shailen, to do this in ASP you should create an instance of your object in the Application_OnStart event procedure in the Global.asa and put the reference to your object in an Application variable that all of your pages can access. Here's an example:
For ASP (Global.asa):
Sub Application_OnStart
Set Application("ourObj") = Server.CreateObject("OurProgID")
End Sub
However, if you are using the .NET Framework, you can do basically the same thing in the Application_Start event procedure in the Global.asax. Here's a sample in Visual Basic®.NET:
For ASP.NET (Global.asax):
Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs)
Application("ourObj") = New ourClass()
End Sub
Internet Explorer WebControls
Q: S. Mohan ponders, "We want to create a custom toolbar that would dock with the Internet Explorer toolbars for our application. Can this be dynamic Internet Explorer served up by the Web server?"
A: There is a set of ASP.NET WebControls called the Microsoft Internet Explorer WebControls that should suit your needs. In the set, there's a really cool toolbar control that's easily implemented in an ASP.NET application. The toolbar can be absolutely positioned or docked to the top of the Internet Explorer window or a containing IFRAME. A variety of controls can be placed on it, and it can be scripted and styled any way you like. The Internet Explorer WebControls are not currently supported, but you can download and install them from the MSDN Download Center.
OnPropertyChange Is Good
Q: Craig writes, "I need to enable a button if the length of the contents in a textbox is greater than 0. I can capture the onkeypress event, bit if the user backspaces or clears out the textbox, I don't get an event and the onchange only fires if the user actually leave the textbox. How can I capture those events?"
A: You should handle the onPropertyChange event. This fires when any property of the textbox is changed, including the contents of the textbox from the keyboard, the mouse, or through script.
<HTML>
<HEAD>
<SCRIPT>
function onPropChange()
{
if (event.propertyName != "value") return;
btn1.disabled = (txt1.value.length == 0)
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE="text" ID="txt1" ONPROPERTYCHANGE="onPropChange()">
<BR>
<INPUT TYPE="button" ID="btn1" VALUE="Button" disabled>
</BODY>
</HTML>
Hello? Are You There?
Q: Dax Westerman asks, "Is there a way for my Web service to detect when a connection from a browser has been dropped before the service can provide its response? I can work around this problem with an acknowledgement from the client, but ultimately this seems to be a convoluted way to solve a (hopefully) straight-forward problem."
A: Dax, look no further than the IsClientConnected property. ASP has had this property on the Response object since the early days, and for a Web service in ASP.NET, you can find it on your Context.Response object. Your Web service can check this property before doing expensive processing and sending back the full response. You can read more about it in the .NET Framework Class Library documentation.
The Web Team
Mark Davis is a software design engineer on the Internet Explorer SDK team. Mark originates from England and is currently training to climb the major summits in the Northwest.
Heidi Housten works as a Consultant with Microsoft Consulting Services in Sweden after spending some time in Developer Support and MSDN. It is only a rumor that she moved there to escape the drizzle of Seattle; she really went for the traditional crayfish parties in August.
Jay Allen, a Support Engineer for the Internet Client team in Microsoft Developer Support, longs for the integration of Notepad and Emacs Lisp. What little time is not consumed by his four children is usually spent reading math books, studying Japanese and programming in Haskell.
Tosh Meston is a Web developer on the Outlook Web Access team. He comes to Microsoft with a background in physics and spends his free time reading and perfecting his three-point shot on the basketball court.