« Read an INI File with C# (Managed) | Main | Mimic Unknown Connection String in .NET Assembly »

Tuesday, August 16, 2005

Anatomy of a ProgressBar for ASP.NET

Note: Code is .NET 1.1

Here is the repost of my previous article and code for implementing a ProgressBar for ASP.NET 1.1 - to highlight why it's not such a simple thing to do.

A ProgressBar in a Windows Form application

First, to understand the challenge of building a Progress Bar Control for a Web application, let's examine how one works in a Windows Forms application for .NET.

Generally speaking, a Windows Forms application will execute within the environment of a single computer. Most Windows Forms applications will also typically only run within a single thread, where the GUI and business logic (behind the scenes) is sharing the same memory space, as accessible objects and classes. Because of this kind of setup, it is relatively easy for the business logic code to report the state of a given execution path to a visible element in the GUI, like a ProgressBar.

The code for the executing business logic simply references the ProgressBar object, while it is running, and "sets" an incremental value for the ProgressBar to indicate progress.

The properties and method of a ProgressBar to take note of, are: Minimum, Maximum, Value (and/or Increment(int)). By repeatedly supplying an incremental Value between Minimum and Maximum, the ProgressBar will graphically indicate where that Value is in relation to Minimum and Maximum.

Simplistically speaking, that continuous setting of information in response to code execution is possible because all the objects and code involved is executing within the confines of the same computer (same memory space, same thread, etc…), where the business logic code and GUI elements are easily accessible to each other.

A ProgressBar in an ASP.NET application

But now consider the web. We've all heard it before, and the fact still remains: the Internet is "stateless" - but why is that important when it comes to implementing a ProgressBar on a Webform?

Because, the business logic code of an ASP.NET application only executes on the Web server, while the active GUI elements of the same application resides in a Web browser (on a separate computer, with separate memory space using separate processor threads, etc…).

This disconnected relationship within a Web application is, for the most part, the opposite of what is happening in many desktop applications (which is why a Windows Forms application is sometimes considered "stateful". Tightly-coupled is another term).

And because a Web application is "stateless" (a.k.a. loosely-coupled), having business logic code communicate with a ProgressBar in the GUI is not easily possible.

What does it mean to have State?

To have State in an application is to imply that two or more objects can understand information about each other, and that they are accessible to each other.

State management in Web applications (for all modern platforms or languages) has progressed in a huge way with the addition many years ago of Session-cookies. And, the advent of ASP.NET and ViewState is arguably the biggest advance in State Management for Web applications since Session-cookies, but we're still not out of the "stateless" woods yet.

Although State Management and ViewState for Web applications is not the primary focus of this article, it is important to understand that the "state" relationship between the Web server and the Web browser is entirely one-sided. Meaning, the server is only aware of the browser, when the browser permits it.

And because of this one-sided "state" relationship, business logic code executing on the server is not readily capable of communicating progress to the GUI.

In a Web application, the browser is always the starting point. Generally speaking, the browser always initiates the starting "request" to the server for a webpage, static resource or a dynamic function. The server, in turn, "responds" by sending some content back in some form or another.

This transaction (a "request" resulting in a "response") is possible with the aid of HyperText Transport Protocol (HTTP). The important point to note here is that the server never starts this conversation. A server cannot "push" response content (or a request for that matter) to a browser unbidden. The internet was not designed for this type of connected state, and for good reason (not covered in this article).

Note: Do not confuse the HTTP "keep-alive" instruction with State Management in this context, this is something different (again, not covered in this article).

Session-cookies provide a mechanism for a server to recognize a browser request as belonging to a specific individual visitor to a website. Session-cookies are how Session Scope variables (stored on the server) can always be available to a specific User of your application. A Session-cookie is the "linkage" for associating a specific browser with specific variables in memory on the server.

ViewState is another mechanism, in addition to Session-cookies, to help link information between data processed and required on the server, with specific GUI elements and fields in a browser. And although the hidden ViewState data in a Webform is created by the server, it is only useful again to the server upon a subsequent "request" from the browser.

So, between Session-cookies and ViewState, an ASP.NET Web application is able to communicate more information between its disconnected parts (namely the GUI and server-side business logic) to ultimately provide a semblance of "state". This is one of the key advantages of ASP.NET over other Web development platforms and languages.

Butthere is still an accessibility break down in our State Management, because connectivity between business logic on the server and the GUI is still entirely dependant on the browser initiating communication (keyword: accessibility).

Given this brief lesson on "state", we should now understand more about the lack of accessibility between our business logic code and the GUI of a Web application. So then, how do you "set" or "push" real-time progress information from the server to the browser to run a ProgressBar? With effort and a new model for receiving and displaying progress read on.

Why even have a ProgressBar for your Webform?

Why? To manage a browser request that results in a long-running process on the server, until the server can send a finished response back.

And why is managing a long-running response important for a Webform? Two reasons:

  1. To give the User a visual indicator that something is happening, that the application is not suddenly broken.
  2. Most modern browsers will timeout after a short period and not wait for the response regardless if it may still potentially show up.

Note: Reason 2 is directly the result of the internet's "stateless" nature. A web browser inherently has no intimate knowledge of a server's availability, and visa versa.

In an ideal world, every request to the server from a Webform will result in a near instant response. This way the User is never left wanting or guessing what is happening.

But as fast as ASP.NET is, eventually a User will request a server-side function that will potentially take a long time to complete. Having a long-running function is not necessarily a bad thing, but not informing the User can be.

Prevent Browser Timeout with HTTP Instructions

In addition to the challenge of one-sided "state" management in a Web application (described above), a long-running server-side process will typically result in the browser simply not wanting to wait. This is a hard-coded fact of most modern browsers.

A browser "request" is typically an HTTP GET or POST command, and a typical "response" from a server is some other HTTP instructions generally followed by some content (usually HTML).

So how do you instruct a browser to wait longer than usual? With some special HTTP response instructions, of course.

HTTP response instructions typically contain general information about the contents the browser is about to receive from the server, such as format, language, caching and size but nothing about how long the content might take to arrive. With such a response, the browser just expects the content will all arrive in a timely manner, else it times out.

Following is a typical HTTP response:

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.1
Date: Fri, 08 Jul 2005 21:15:24 GMT
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 9819

(html appears here)...

So what HTTP instructions do you use to tell a browser to wait on a long running process? AND manage a ProgressBar display for the user? It begins with the following response:

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.1
Date: Fri, 08 Jul 2005 21:21:56 GMT
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Transfer-Encoding: chunked
Cache-Control: private
Content-Type: text/html; charset=utf-8

1
<
4
html
1
>
1
<
4
body
1
>
...

The main difference in HTTP response instructions, from the special one we need from the first "typical" response is the following:

  • The addition of Transfer-Encoding: chunked
  • No total Content-Length:
  • The contents (after the empty line, a.k.a. two carriage-returns) is now broken into pieces surrounded by line length numbers.

By sending special chunked instructions to the browser we can make it wait almost indefinitely. As well, in the contents of the same response output we can feed incremental instructions (as it becomes available on the server) in the form of JavaScript commands embedded in HTML.

So, how do you send these kinds of HTTP instructions and JavaScript commands, and run a ProgressBar, using ASP.NET?

Prevent Browser Timeout and display a ProgressBar in ASP.NET

If you're still following along, the question of why Visual Studio .NET doesn't ship with a ProgressBar Control for ASP.NET Webforms (like it does for Windows Forms), should be more apparent.

A ProgressBar for Webforms cannot operate the same as for Windows Forms because of "state", and lack thereof. The solution requires a slightly different processing model as provided by the chunked HTTP instructions described above.

The processing model of an ASP.NET ProgressBar can be similar to Windows Forms, except in how the server will "feed" the incremental values to the browser.

Therefore, my ASP.NET ProgressBar solution involves breaking the Control into two reusable parts:

  1. ProgressClient (for the browser, as a User Control)
  2. ProgressServer (for the server, as a Class).
Progressbarproject

The Visual Studio .NET 2003 Source-code

Without going into too much detail with the source-code (which you'd rather read yourself I'm sure), this solution, written in C#, comes complete with a two ProgressBar demo implementation examples.

One simple demo (waiting on a sleepy thread in a for loop), and one slightly more complex demo (transferring a file from a one website to another, and displaying the progress).

Each example comprises two Webforms:

  1. The first Webform (Demo*.aspx) is for the User to trigger the long-running process (and subsequently display its results). This Webform contains the ProgressClient User Control.
  2. And the second Webform (MyWorkerCode*.aspx) contains the worker code that will execute on the long-running process, and report its progress in real-time. This Webform uses the ProgressServer Class to communicate with the ProgressClient.

Setup the ProgressClient

In the code-behind of the Demo1.aspx Webform, the following code highlights how to manage the properties of the ProgressClient.

private void Page_Load(object sender, System.EventArgs e)
{
    progressClient1.StartButtonText = "Call Sleepy Thread";
    progressClient1.StartButtonTextWhenFinished = "Reset Webform for Demo";
    progressClient1.StartButtonCommandWhenFinished = "location.href = 'Demo1.aspx'";
    progressClient1.WorkerWebformUrl = "MyWorkerCode1.aspx";
    progressClient1.NoticeBeforeWorkerStart = "Click to start.";
    progressClient1.NoticeAfterWorkerComplete = "Thread completed.";
}

How the ProgressClient Runs

When the Webform is displayed in the browser, it should look like the following.

Progressbarstart

With the help of a non-visible HTML IFRAME element, and some embedded client-side JavaScript functions, the request to the server is initiated, which displays the ProgressBar. And as the server responds with incremental information, via those special HTTP instructions, the graphical ProgressBar indicator changes.

Note: The call to the worker code on the server is initiated without refreshing (redisplaying) the Webform.

Progressbarrunning

Upon reaching the 100% increment from the server, the ProgressBar disappears and resets to start over, or to allow starting a different command.

Progressbarfinished

Setup the ProgressServer

Following is a simple example to highlight how easy it should be to integrate reporting progress information with your server-side code.

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

    // invoke the server half of the progressbar user control
    ProgressServer progressServer = new ProgressServer();

    try{
        // start output for ProgressBar
        progressServer.Begin();
        progressServer.Maximum = 100;

        for(int i = 1; i <= 100; i++){

            // notify webform of progress
            progressServer.Increment(i, String.Format("Loop {0} of 100...", i.ToString()));

            // purposely create a pause in the loop
            System.Threading.Thread.Sleep(50);

        }
        // end output for ProgressBar
        progressServer.End();

    }catch(ThreadAbortException){
        // do nothing because Response.End() was called.
    }catch(Exception error){
        // send error information to client
        progressServer.SendMessageAndReset(error.ToString());
    }

}

How the ProgressServer Works

As described above in the HTTP sections, the chunked command is the first instruction for the browser, this is managed in the ProgressServer Method Begin(), which initializes an HtmlTextWriter tied to the underlying Response.Output Stream. Following is the C# code:

public void Begin(){
    // create the unbuffered output
    // for beginning of step actions
    htmlOutput = new HtmlTextWriter(context.Response.Output);
    context.Response.Buffer = false;
    htmlOutput.WriteFullBeginTag("html");
    htmlOutput.WriteFullBeginTag("body");
}

Note: The intrinsic Response.Buffer property (false) is what provides the workings to issue the Transfer-Encoding: chunked instructions.

Next, the Increment() Method is about sending the incremental information to the ProgressClient to advance the graphical indicator, and as a bonus also send a text message to accompany it. This is achieved by repeadtedly calling the Flush() Method on the HtmlTextWriter and the underlying Response.Output Stream. The Method includes divisor logic to manage the increment sent in percentages (when the Maximum property is greater than the default 100), so never more than 100 commands are sent from the server to the browser. This is useful in the case of measuring progress based on the number of bytes of when moving or copying a large file (see Demo2 in the source code).

public void Increment(int increment, string message){
    // manage increment work in percentages only
    // because the visual display of the progress bar
    // will only reach to 100% in width
    double progressNow = 0;
    if(maximum > 10000){
        progressNow = Math.Floor(((increment/100) * 100) / (maximum/100));
    }else if(maximum > 1000){
        progressNow = Math.Floor(((increment/10) * 100) / (maximum/10));
    }else{
        progressNow = Math.Floor((increment * 100) / maximum);
    }
    if(progressNow > progressCounter){
        // ensure the output message text is not empty
        if(message == null || message.Trim() == String.Empty){
            message = "(message is empty)";
        }
        // percentage value has incremented by a whole number
        // equal or between 1 and 100
        // so send progress report to client
        htmlOutput.WriteFullBeginTag("script");
        htmlOutput.Write(String.Format("window.parent.SetProgress({0}, '{1}');", progressNow.ToString(), message.Replace("\\", "\\\\").Replace("'", "\\'").Replace(Environment.NewLine, "\\n")));
        htmlOutput.WriteEndTag("script");
        htmlOutput.Flush();

        // increment counter for next percentage increment
        progressCounter++;
    }
}

Lastly, to clean up the chunked output sent from the server, is the overloaded End() Method, where one Method can send an optional message prompt to the ProgressClient, which may be helpful to send special information about the end of whatever long-running process just completed.

The call to Response.End() ensures no other HTML content from the original *.aspx file is sent, to reduce unnecessary (and unseen) output.

public void End(){
    End(null);
}
public void End(string message){
    // insert ending message prompt
    if(message != null){
        if(message.Trim() == String.Empty){
            message = "(empty string)";
        }
        htmlOutput.WriteFullBeginTag("script");
        htmlOutput.Write(String.Format("window.parent.Say('{0}');", message.Replace("\\", "\\\\").Replace("'", "\\'").Replace(Environment.NewLine, "\\n")));
        htmlOutput.WriteEndTag("script");
    }
    // end of progress output
    htmlOutput.WriteEndTag("body");
    htmlOutput.WriteEndTag("html");
    htmlOutput.Flush();
    context.Response.End();
}

Client Options

In my implementation, one of my main objectives was to display real-time information from the server, without refreshing the browser continuely, or even once.

The ProgressClient (in the browser) is using a seemingly hidden HTML IFRAME to manage receiving increment instructions from the server. Although this method may suggest it is not entirely cross-browser, it does work for newer versions of Mozilla and Firefox, and obviously all versions of MS Internet Explorer. Using an IFRAME allows the initial call to the server-side worker code by simply calling the Webform to have it load in the IFRAME (so the current Webform is not refreshed). Then the increment instructions can arrive using HTML and JavaScript at their leasure. These JavaScript commands simply reference the ProgressClient objects and functions by appending window.parent... to each command.

Other options for calling the server without refreshing the current Webform in the browser are:

  1. Dynamic manipulation of an HTML SCRIPT tag.
  2. Utilizing the XMLHttpRequest object, also known lately as Ajax (what a dumb term).

Manipulating an HTML SCRIPT tag works by dynamically assigning a new value for the SRC attribute. This can permit dynamic calls to a server without affecting the load state of a Webform in the browser. In turn, the returning server output can contain pure client-side JavaScript code or function calls (all in context to the current webpage). But...unfortunately this "feature" only works well in MS Internet Explorer (very gracefully I might add). Mozilla and Firefox appears to exhibit some odd behavior when you attempt this (undoubtedly a security feature, maybe?).

Using XMLHttpRequest (a.k.a. XMLHTTP or Remote Scripting) is another option, that is lately cross-browser and a wonderful solution to many browser to server challenages - but in this case has some limitations. The behavior of XMLHttpRequest and it's readystate property implies that it also wants to receive server responses in a "complete" timely manner. Although there are some tricks that can be applied to XMLHttpRequest to get it to receive response content in a delayed context, I did not find this approach very cross-browser friendly.

In Summary

Of course, there may be cases where this type of real-time progress reporting from the server is not entirely practical. Some examples would be where the beginning of the long-running process does not even start for some time (like when waiting on results from a Database query before beginning other processing). In which case, you need to handle sending at least a starting increment to show something and maybe a message that says "Stand by".

Another example where this type of progress reporting may need a different model, is when initiating a new asynchronous process on the server.  Reporting from a separate thread has different challenges, but throwing a while loop (and some logic to reset the ProgressBar) in to the mix may at least provide some running feedback that could be helpful, until the separate thread finishes.

A Composite ProgressBar Control for ASP.NET

This article presented just one way of implementing a ProgressBar Control for ASP.NET Webforms, by simply utilizing a custom User Control and a separate Class file. This approach of course has some limitation. But mainly I was hoping a User Control would also be helpful to some, because my example is also limited in extended functionality, and that at least this way others could make their own modifications more easily.

Please send me or post your feedback, about any implementation challenages you have, or feature suggestions, so that I may better prepare a distributable (more feature rich) version of this Webform ProgressBar in the near future, as a Composite Control.

Thank you for reading and thank you for the complimentry comments posted with the original positing.

DemoWebProgressBar.zip DemoWebProgressBarVB.zip

TrackBack

TrackBack URL for this entry:
http://www.typepad.com/t/trackback/1084389/6153381

Listed below are links to weblogs that reference Anatomy of a ProgressBar for ASP.NET:

Comments

Great Job, Thanks for posting your valuable work...

i really loved your work & the way you explained problem of state less. once again i will say its a great job.

Thanks
Raja

Hi
i tried to implement your progress for a button click event instead of calling some other page by using progressClient1.WorkerWebformUrl ="page2.aspx"

can u please suggest me the best option to my problem

Thanks in advance.

Post a comment

If you have a TypeKey or TypePad account, please Sign In

Programmer for Hire

  • About Hiring Me:
  • Contact Information:
    • Name: P. Scott Cadillac
    • Phone: (902) 624-1266
    • Email: scott@xmlx.net
    • Location: Mahone Bay, Nova Scotia Canada
    • Timezone: Atlantic, ADT
  • Special Links: