ASP.NET Article        
Skip Navigation Links
Home
Articles
Code
Applications
Bookshelf
CV/Resume

ASP.NET WaitPage Framework (3/5)


[Note: Code is for ASP.NET 1.1, but converts to ASP.NET 2.0]

Download code and reference documentation for this article:
WaitPageFramework.zip (106KB)

View WaitPage Reference Documentation online

2. The Wait Page Mechanism in Detail

This section will first briefly explain how the framework works overall, with some snippets of the main function implementations to aid understanding.

After this, each interface and class will be described in a little more detail, together with information as to how each public method and event plays a part in the overall framework.

First, to recap from the previous section, the way in which the Wait-page framework is set up is very simple: - First the button (or other postback) handler starts the long running task asynchronously. - It then registers details of this task with the wait page mechanism. - The wait page mechanism displays a user-defined wait page webform, which refreshes itself periodically. - Whenever it's refreshed, the wait page calls back to the mechanism to check whether the task being waited upon has finished yet, and whether the results are ready. If so, the mechanism redirects to a results page (which could be the original page), and the results are displayed.

The classes in the framework and their main functions are shown in figure 2 below.

 

Figure 2 - Wait Page Framework Class Diagram

There are 2 interfaces, ITaskWaiter and IWaitPage.

- An object implementing ITaskWaiter is registered with the framework, and is used to wait for the task to finish, and to obtain the results when it does. - IWaitPage is implemented by the WaitPage Web Form class being used, and allows the framework to display an optional progress message.

There is also a basic implementation of most of the ITaskWaiter interface, in the class called BaseTaskWaiter. In most cases it should be sufficient to derive from this class. It is abstract, and at least 3 methods need to be overridden to provide the correct functionality.

WaitPageHelper is the main coordinating class in the Wait Page Framework, and the one that does all the actual work. It consists solely of static methods which are called to register the ITaskWaiter-implementing object and to start and maintain the waiting process, amongst other things.

Figure 3 below shows a sequence diagram which shows the sequence of events in the example used in the previous section. This is typical of the interactions between the objects in the framework and those defined in an application using the framework.

 

Figure 3 - Typical Sequence of interaction in the Wait Page Framework

The most important methods in the Wait Page Framework are the StartWaiting and DoWait methods, in the WaitPageHelper class. As you can see from the sequence diagram, StartWaiting starts the whole process, and causes the framework to enter the waiting loop, via the WaitPage WebForm.

The StartWaiting method is implemented as shown below:

        public static void StartWaiting()
        {
            string url = GetWaitPageURL();
            HttpContext.Current.Response.Redirect(url);
        }

It uses the helper function, GetWaitPageURL:

    /// <summary>
    /// Helper method to return the appropriate URL for the
    /// currently registered ITaskWaiter.
    /// </summary>
    /// <returns>the appropriate WaitPage URL</returns>
    private static string GetWaitPageURL()
    {
        if (!ExistsCurrentTask())
        {
            throw new WaitPageInvalidContextException();
        }

        string waitPageURL = CurrentTaskWaiter.GetWaitPageURL();
        // if no specific wait page supplied, use default (which was 
        // registered with the WaitPageHelper)
        if (waitPageURL == null)
        {
            waitPageURL = _defaultWaitPageURL;
        }
        return waitPageURL;
    }

All that StartWaiting does is to get the wait page URL (which can be supplied by the current ITaskWaiter if necessary – otherwise a default wait page URL is used), and then redirect to this URL.

How does redirecting to the wait page cause the waiting loop to start?

One thing that has to be done in the any WaitPage WebForm used with this framework, is that it should call the DoWait method in WaitPageHelper.

In the example application in the previous section, this call looked like this:

        private void Page_Load(object sender, System.EventArgs e)
        {
            Response.Cache.SetNoStore();

            try
            {
                WaitPageHelper.DoWait(this);
            }
            catch (WaitPageInvalidContextException)
            {
                // hide the waiting message and display a
                // panel containing an error message
                pnlError.Visible = true;
                pnlWaitMessage.Visible = false;
            }
        }
    }

DoWait will either end up redirecting to the result page if the task has finished, or set a refresh header in the current page (which, if called from the Wait Page, will be the Wait Page) for 2 seconds.

The implementation of DoWait looks like this:

        public static void DoWait(IWaitPage waitPage)
        {
            if (!ExistsCurrentTask())
            {
                throw new WaitPageInvalidContextException();
            }

            if (IsCurrentTaskFinished())
            {
                RedirectToResultsPage();
            }
            else
            {
                // set wait page label
                waitPage.SetWaitingMessage(CurrentTaskWaiter.GetWaitPageMessage());

                // TODO: make number of secs to retry configurable
                HttpContext.Current.Response.AddHeader("Refresh", "2");
            }
        }

If the task hasn’t finished, it sets the waiting message on the page (obtaining the correct waiting message from the current ITaskWaiter via the GetWaitPageMessage method), and then adds the Refresh header, so that the WaitPage refreshes itself after 2 seconds, and starts the whole process again.

If the task has finished, it calls RedirectToResultsPage, the implementation of which looks like this:

        private static void RedirectToResultsPage()
        {
            if (!ExistsCurrentTask())
            {
                throw new WaitPageInvalidContextException();
            }

            CurrentTaskWaiter.NotifyResultsReadyForPage(true);
            string resultsPage = CurrentTaskWaiter.GetResultsPageURL();

            HttpContext.Current.Response.Redirect(resultsPage);
        }

The first thing that happens is the current ITaskWaiter is notified that the results are ready, via the NotifyResultsReadyForPage method. The ‘true’ parameter value indicates that the task has completed successfully, and that results are available (‘false’ would indicate it had been cancelled).

What happens as a result of this notification is dependent on the implementation of ITaskWaiter – however, the BaseTaskWaiter implementation fires an event, NotifyResultsStatus.This can be very useful if the page used to show results is also the page used to initiate the long running task. Typically, in this case, you would handle the event with a static handler, and store the ‘results ready’ flag in a session variable. The example application in the previous section did this, and you can refer back to the details there.

Next, the results page URL is obtained from the current ITaskWaiter, and a redirect is performed to this URL. The details of what you typically need to implement in the results page are at the end of the previous section.

That completes the detailed examination of how the framework works. The next part of this section describes each of the interfaces and classes, along with their public methods, properties and events.

ITaskWaiter

ITaskWaiter contains methods which allow the WaitPageHelper to determine if the task has finished, get results and send notification that results are ready, and to get an (optional) progress message. It also provides the URL which the mechanism will redirect to as soon as the results of the long running task are available.
    public interface ITaskWaiter
    {
        bool IsTaskFinished();

        object GetResults();

        string GetResultsPageURL();

        void NotifyResultsReadyForPage(bool bResultsReady);

        // if null is returned, default wait page is used
        string GetWaitPageURL();

        string GetWaitPageMessage();
    }

As mentioned in the previous section, the ITaskWaiter is only intended to provide an interface for waiting for a task to finish once it's started - the WaitPageHelper does not start the task, but assumes this has been done before it's DoWait function is called. However, typically a StartTask method would also be included in the class implementing ITaskWaiter. See the previous section for an example.

IsTaskFinished is simple - it needs to return true if the task has finished, false otherwise. Of course, how it does this may be fairly involved, depending on how and where the long running process is running.

Likewise, GetResults may be quite involved, but ultimately all it needs to do is return the results as an object. The results are cast to the appropriate type by the designated Results Page.

GetResultsPageURL returns the URL to which the mechanism redirects once the task has finished and the results have been obtained.

NotifyResultsReadyForPage is called by the framework to indicate that the results are ready and can be picked up. The implementation of this function would typically fire an event at this point, to which the results page had subscribed to. The BaseTaskWaiter class contains a delegate, event and an EventArgs class to provide a basic implementation of this. The example implementation in the previous section shows more details.

GetWaitPageURL is used in case a different WaitPage is required for a particular task waiter, instead of the default one that the wait page mechanism is initially set up with (see below). For example, some tasks might have a cancel capability, and so an alternative page with a cancel button could be used for these tasks.

If this returns null, the system falls back to using, in this order: 1) the WaitPage URL registered with the WaitPageHelper (using the RegisterDefaultWaitPageURL function) 2) If no URL has been registered with this function, it comes from the AppSetting “DefaultWaitPageURL” in the web.config file. 3) If this AppSetting is not set, a hardcoded WaitPage URL of "~/WaitPage.aspx" is used.

Finally, GetWaitPageMessage is used in order to obtain a specific static message for display in the wait page which pertains to the task in question (e.g. 'Processing Orders'). It can also be used to display a dynamically changing progress message (‘dynamic’ in the sense that it changes on each refresh of the wait page).

BaseTaskWaiter

    public abstract class BaseTaskWaiter : ITaskWaiter
    {
        public BaseTaskWaiter(
            string resultsPageURL
            )
        

        public BaseTaskWaiter(
            string resultsPageURL,
            bool bShowProgress
            )

        public BaseTaskWaiter(
            string resultsPageURL, 
            string waitPageMessage, 
            bool showProgress
            )

        public event NotifyResultsStatusEventHandler NotifyResultsStatus;

        public virtual string ProgressMessage
        {
            get…
            set…
        }

        #region ITaskWaiter Members

        public abstract bool IsTaskFinished();

        public abstract object GetResults();

        public virtual string GetResultsPageURL()

        public virtual void NotifyResultsReadyForPage(bool bResultsReady)

        // returns null by default
        public virtual string GetWaitPageURL()

        // returns wait page message, plus ProgressMessage if showProgress has
        // been set to true
        public virtual string GetWaitPageMessage()

        #endregion

    }

BaseTaskWaiter is an abstract class which implements the ITaskWaiter and provides some of the implementation. It has a default waiting message, and also allows progress messages to be set. In the constructor, you must pass in the URL of the results page, and optionally a custom waiting message to override the default one if necessary, and a flag to indicate whether you want to use the progressMessage feature.

When deriving from this class to make a custom TaskWaiter object, you need to implement IsTaskFinished to determine when the task finishes, and GetResults to fetch the actual results of the long running task once it has actually finished.

BaseTaskWaiter also contains an event NotifyResultsStatus, which is fired in response to a call to the NotifyResultsReadyForPage method by the WaitPageHelper. For more details on how this is used, refer to the previous section, which has an example of handling this event. The documentation for the framework also has more information.

IWaitPage

    public interface IWaitPage
    {
        void SetWaitingMessage(string waitMessage);
    }

IWaitPage is a very simple interface with just one method, which has to be implemented by the Wait page(s) used in your application. It's sole purpose is to allow the Wait page mechanism to write out an optional progress message. This mechanism could easily be adapted to display a progress bar or something similar.

As mentioned above, IWaitPage just has one method, which allows a 'waiting message' or progress message to be set each time the wait page is redisplayed. The waiting message is obtained by the WaitPageHelper from the ITaskWaiter during each refresh period. This means it's easy to supply some indication of progress, if the long running process can provide this information in the first place. This is done by having the ITaskWaiter query the long running process for progress information each time it gets asked to supply a waiting message, and then incorporating the information into the waiting message. For example, if the long running task is processing 30 orders, and it reports back it's on order 12, you can construct a string like "processing order 12 of 30".

WaitPageHelper

    public class WaitPageHelper
    {

        public static void RegisterDefaultWaitPageURL(string waitPageURL)

        public static bool RegisterTaskWaiter(ITaskWaiter taskWaiter)

        public static object GetCurrentTaskResults()

        public static void CancelCurrentTask()

        public static void StartWaiting()

        public static string StartWaitingWithRedirectDelay()

        public static void DoWait(IWaitPage waitPage)
    }

The WaitPageHelper is the main coordinator class of the entire framework. It allows the registration of an ITaskWaiter object, checks this regularly to see whether the task has finished, and redirects to the results page when this happens.

The main public methods in WaitPageHelper which must be used when using the framework are:

bool RegisterTaskWaiter(ITaskWaiter taskWaiter) 
This is used just after the originating page starts the task, and registers the corresponding ITaskWaiter object with the WaitPage framework. It returns true if the task was registered, and false if the taskWaiter couldn’t be registered, if, for example, another task already exists.

void StartWaiting()
This starts the whole WaitPage process going by redirecting to the wait page associated with the current registered ITaskWaiter. This should be called from the initiating postback handler, after registering the taskWaiter and starting the long running task asynchronously.

void DoWait(IWaitPage waitPage)
This is normally called from the WaitPage, specifically from the PageLoad function - it determines whether the Task has finished, by querying the ITaskWaiter. If it hasn't finished, it sets the waiting message on the wait page, and sets a Refresh HttpHeader so that the WaitPage refreshes itself, and calls DoWait again. If it has finished, it will get the ITaskWaiter to fire the NotifyResultsReady event (the handler of which typically will set a flag which the results page can read) and redirects to the result page.

object GetCurrentTaskResults()
This is called from the results page, if results have been notified as being ready. The results are obtained via the ITaskWaiter, and should be cast by the caller to the appropriate type. By calling this, the current task waiter is cleared, and the whole task waiting process is cancelled - so you need to keep hold of the results if you require them later, as you won't be able to get them again. You also must make sure you call GetCurrentTaskResults, otherwise the task won't be cleared.

void CancelCurrentTask()
Cancels the current task, and signals to any interested parties, via ITaskWaiter.NotifyResultsReadyForPage, that no results are coming.

void RegisterDefaultWaitPageURL(string waitPageURL)
The wait page URL used comes from the ITaskWaiter. If this is not specified, it comes from the URL specified by this function. If no URL has been registered with this function, it comes from the AppSetting “DefaultWaitPageURL” in the web.config file. If this AppSetting is not set, a hardcoded WaitPage URL of "~/WaitPage.aspx" is used.
< prev   1 | 2 | 3 | 4 | 5   next >
© 2006 Pete Beech