Av rating:
Total votes: 69
Total comments: 7


Gaidar Magdanurov
Implementing Waiting Pages in ASP.NET
16 July 2007

One thing users do not like about Web-applications is waiting for some long-running process to finish without knowing if it is really working or not. There are few greater frustrations than looking at the screen and think “What’s going on?”, while the application performs some undercover operation. That is why application should provide user with feedback, showing that something is happening and, telling the user that he will have to wait just a little bit longer.

If the process is running synchronously you have no options – the user will have to wait while page is reloading anyway. But then an asynchronous process comes to play, you may like to work out various solutions for a better user experience. In this article we will discover a few ways to create a waiting page.

Architecture of the waiting page solution

Before we start to build a waiting pages, we have to decide on architecture of the solution. The easiest to implement solution is shown at Fig. 1.

Fig. 1.Waiting page architecture

The main page starts a new thread and assigns a unique ID to it, then redirects user to the waiting page, passing ID to the waiting page to enable waiting page to track progress of the process running in a thread which was started by the main page. The process submits results to a special controller object that contains collection of key-value pairs to identify results submitted by threads with different IDs. The waiting page queries the controller to check if the process is still running or has already finished.

Solutions

Now we are set with the architectural decision and are ready to start building waiting page. Let’s go from the easiest solution to the more complex ones.

The simplest solution

The simplest solution to implement is which does not require tracking real progress of asynchronous process, thus showing only two states of the process – still running or already finished.

At first, we should create a controller object that can simply provide the waiting page with the state of the request.

The controller

using System;

using System.Collections;

 

 

public static class SimpleProcessCollection

{

    private static Hashtable _results = new Hashtable();

 

    public static string GetResult(Guid id)

    {

        if (_results.Contains(id))

        {

            return Convert.ToString(_results[id]);

        }

        else

        {

            return String.Empty;

        }

    }

 

    public static void Add(Guid id, string value)

    {

        _results[id] = value;

    }

 

    public static void Remove(Guid id)

    {

        _results.Remove(id);

    }

}

The main page should assign the asynchronous process with an unique ID and pass this ID to the waiting page, then waiting page will query SimpleProcessCollection for the result with the given ID. The process, in turn, should add the result to the SimpleProcessCollection to notify waiting page that the process ended.

The main page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.aspx.cs"
Inherits="Simple_Start" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Simple Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        <asp:Button ID="btnStart" runat="server" Text="Start Long-Running Process"
OnClick="btnStart_Click" />

    </div>

    </form>

</body>

</html>

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Threading;

 

public partial class Simple_Start : System.Web.UI.Page

{

    protected Guid id;

 

    protected void btnStart_Click(object sender, EventArgs e)

    {

        // assign an unique ID

        id = Guid.NewGuid();

        // start a new thread

        ThreadStart ts = new ThreadStart(LongRunningProcess);

        Thread th = new Thread(ts);

        th.Start();

        // redirect to waiting page

        Response.Redirect("Status.aspx?ID=" + id.ToString());

    }

 

    // this is a stub for a asynchronous process

    protected void LongRunningProcess()

    {

        // do nothing actually, but there should be real code

        // for instance, there could be a call for a remote web service

        Thread.Sleep(9000);

        // add result to the controller

        SimpleProcessCollection.Add(id, "Some result.");

    }

}

The waiting page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Simple Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

        <div>

        <p align="center">

            <asp:Image ID="ImageStatus" ImageUrl="~/Images/Wait.gif"
runat="server" /></p>

            <h1>

                <asp:Label ID="lblStatus" runat="server"
Text="Working... Please wait..."></asp:Label>

            </h1>

        </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

public partial class Simple_Status : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        // chech if the page was called properly

        if (Request.Params["ID"] != null)

        {

            Guid id = new Guid(Request.Params["ID"]);

            // check if there is a result in the controller

            if (SimpleProcessCollection.GetResult(id) == String.Empty)

            {

                // no result - let's refresh again

                Response.AddHeader("Refresh", "3");

            }

            else

            {

                // everything's fine, we have the result

                lblStatus.Text = "Job is done.";

                ImageStatus.Visible = false;

            }

        }

        else

        {

            Response.Redirect("Start.apsx");

        }

    }

}

You can see that the solution is very simple and required just a dozen lines of code. As we are free to use an animated gif on the waiting page, the user will have fun while waiting for the process to complete. The waiting page discussed is presented at Ffig. 2.

 

Fig. 2. The simplest waiting page.

Waiting for more than one process

If there is more than one process running in the background waiting page should wait for, then it is necessary to implement some kind of progress bar control and extend the sample shown above to handle more than one process. To do that, we can implement a simple counter of processes that are still running as shown in the following code snippet.

The controller

public static class MultiProcessCollection

{

    private static Dictionary<Guid, int> _results =
                        new Dictionary<Guid, int>();

 

    public static int GetProgress(Guid id)

    {

        if (_results.ContainsKey(id))

        {

            return _results[id];

        }

        else

        {

            return 0;

        }

    }

 

    public static bool IsCompleted(Guid id)

    {

        return (GetProgress(id) == 0);

    }

 

    public static void Add(Guid id)

    {

        if (!_results.ContainsKey(id))

        {

            _results.Add(id, 0);

        }

        _results[id] = _results[id] + 1;

    }

 

    public static void Remove(Guid id)

    {

        if (_results.ContainsKey(id) && _results[id] > 0)

        {

            _results[id] = _results[id] - 1;

        }

    }

}

This time controller increments the counter then a process is registered and decrements the counter as a process notifies the controller that it is finished. Thus, code for the main page and for the waiting page will be a little bit more complex.

The main page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.aspx.cs"
Inherits="Simple_Start" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Progress Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        <asp:CheckBox ID="cbProgress" runat="server" Text="Show Progress" /> &nbsp;

        <asp:Button ID="btnStart" runat="server"
Text="Start Three Long-Running Processes" OnClick="btnStart_Click" />

    </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Threading;

 

public partial class Simple_Start : System.Web.UI.Page

{

    protected Guid id;

 

    protected void btnStart_Click(object sender, EventArgs e)

    {

        id = Guid.NewGuid();

 

        MultiProcessCollection.Add(id);

        ThreadStart ts = new ThreadStart(LongRunningProcess1);

        Thread th = new Thread(ts);

        th.Start();

 

        MultiProcessCollection.Add(id);

        ts = new ThreadStart(LongRunningProcess2);

        th = new Thread(ts);

        th.Start();

 

        MultiProcessCollection.Add(id);

        ts = new ThreadStart(LongRunningProcess3);

        th = new Thread(ts);

        th.Start();

 

        if (cbProgress.Checked)

        {

            Response.Redirect("Progress.aspx?ID=" + id.ToString());

        }

        else

        {

            Response.Redirect("Status.aspx?ID=" + id.ToString());

        }

    }

 

    protected void LongRunningProcess1()

    {

        Thread.Sleep(3000);

        MultiProcessCollection.Remove(id);

    }

    protected void LongRunningProcess2()

    {

        Thread.Sleep(7000);

        MultiProcessCollection.Remove(id);

    }

    protected void LongRunningProcess3()

    {

        Thread.Sleep(5000);

        MultiProcessCollection.Remove(id);

    }

}

The waiting page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Progress Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

        <div>

            <h1>

                <asp:Label ID="lblStatus" runat="server"
Text="Working... Please wait..."></asp:Label>

            </h1>

        </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

public partial class Simple_Status : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Request.Params["ID"] != null)

        {

            // check for result

            Guid id = new Guid(Request.Params["ID"]);

            if (!MultiProcessCollection.IsCompleted(id))

            {

                Response.AddHeader("Refresh", "1");

            }

            else

            {

                lblStatus.Text = "Job is done.";

            }

        }

        else

        {

            Response.Redirect("Start.aspx");

        }

    }

}

This time we can make the user experience a little bit better by showing the progress bar indicating the real progress. To do this we implement a simple progress bar control and use it on a modified waiting page.

The progress bar control

using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Drawing;

 

namespace MyControls

{

 

    public class SimpleProgressControl : WebControl

    {

        private int _Max;

 

        public int Max

        {

            get { return _Max; }

            set { _Max = value; }

        }

 

        private int _Value;

 

        public int Value

        {

            get { return _Value; }

            set { _Value = value; }

        }

 

        protected override void Render(HtmlTextWriter writer)

        {

            writer.AddAttribute(HtmlTextWriterAttribute.Width,
                this.Width.Value.ToString());

            writer.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "0");

            writer.AddAttribute(HtmlTextWriterAttribute.Cellspacing, "0");

            writer.RenderBeginTag(HtmlTextWriterTag.Table);

 

            writer.AddAttribute(HtmlTextWriterAttribute.Height,
                                  this.Height.Value.ToString());

            writer.RenderBeginTag(HtmlTextWriterTag.Tr);

 

            for (int i = 0; i < _Max; i++)

            {

                // background color

                if (i < _Value)

                    writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor,
                         ColorTranslator.ToHtml(this.ForeColor));

                else

                    writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor,
                              ColorTranslator.ToHtml(this.BackColor));

 

                writer.RenderBeginTag(HtmlTextWriterTag.Td);

                writer.RenderEndTag(); // td

            }

 

            writer.RenderEndTag(); // tr

            writer.RenderEndTag(); // table

        }

 

 

    }

}

The more advanced waiting page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Progress.aspx.cs"
Inherits="Multi_Progress" %>

<%@ Register TagPrefix="my" Namespace="MyControls" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Progress Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

        <div>

        <my:SimpleProgressControl ID="ctlProgress" runat="server"
BackColor="blue" ForeCOlor="red" Max="3" Value="0" Width="300" Height="30" />

        <h1>

            <asp:Label ID="lblComplete" runat="server" Text="Process complete."
Visible="false"></asp:Label></h1>

        </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

public partial class Multi_Progress : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Request.Params["ID"] != null)

        {

            Guid id = new Guid(Request.Params["ID"]);

            int p = MultiProcessCollection.GetProgress(id);

            ctlProgress.Value = 3 - p;

            if (p != 0)

            {

                Response.AddHeader("Refresh", "1");

            }

            else

            {

                lblComplete.Visible = true;

            }

        }

        else

        {

            Response.Redirect("Start.aspx");

        }

    }

}

This page looks like the shown one on fig. 3.

Fig. 3. More advanced waiting page.

Returning custom data objects from the asynchronous processes

The next step on the way to building more advanced waiting page is to modify the controller object to work with custom data objects thus enable asynchronous process to return these custom data objects and provide the waiting page with more data about the state of the process.

For instance, if the process can be split into a few different steps it may notify the waiting page about the percentage of its completeness – this value can be stored in a field of custom data object used.

The custom data object

using System;

using System.Data;

using System.Configuration;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

 

public class FeedbackObject

{

 

    private string _result1 = String.Empty;

 

    public string Result1

    {

        get { return _result1; }

        set { _result1 = value; }

    }

    private string _result2 = String.Empty;

 

    public string Result2

    {

        get { return _result2; }

        set { _result2 = value; }

    }

 

    private int _progress = 0;

 

    public int Progress

    {

        get { return _progress; }

        set { _progress = value; }

    }

 

 

    public bool Complete

    {

        get { return (_progress == 100); }

    }

}

 

The controller

using System;

using System.Collections;

 

 

public static class FeedbackProcessCollection

{

    private static Hashtable _results = new Hashtable();

 

    public static FeedbackObject GetResult(Guid id)

    {

        if (_results.Contains(id))

        {

            return (FeedbackObject)_results[id];

        }

        else

        {

            return null;

        }

    }

 

    public static void Add(Guid id, FeedbackObject stat)

    {

        _results[id] = stat;

    }

 

    public static void Remove(Guid id)

    {

        _results.Remove(id);

    }

}

To use this features we have to modify the waiting page and the main page.

The main page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Start.aspx.cs"
Inherits="Simple_Start" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Feedback Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        <asp:Button ID="btnStart" runat="server" Text="Start Long-Running Process"
OnClick="btnStart_Click" />

    </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

using System.Threading;

 

public partial class Simple_Start : System.Web.UI.Page

{

    protected Guid id;

 

    protected void btnStart_Click(object sender, EventArgs e)

    {

        id = Guid.NewGuid();

        ThreadStart ts = new ThreadStart(LongRunningProcess);

        Thread th = new Thread(ts);

        th.Start();

 

        Response.Redirect("Status.aspx?ID=" + id.ToString());

    }

 

    protected void LongRunningProcess()

    {

        FeedbackObject fo = new FeedbackObject();

        FeedbackProcessCollection.Add(id, fo);

        for (int i = 0; i <= 100; i++)

        {

            Thread.Sleep(50);

            if (i == 100)

            {

                fo.Result1 = "First result.";

                fo.Result2 = "Second result.";

            }

            fo.Progress = i;

        }

    }

}

The waiting page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status" %>

<%@ Register TagPrefix="my" Namespace="MyControls" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Feedback Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

        <div>

            <my:simpleprogresscontrol id="ctlProgress" runat="server"

backcolor="blue" forecolor="red" max="100"
value="0" width="300" height="30" />

            <h1>

                <asp:Label ID="lblComplete" runat="server"
Text="Process complete." Visible="false"></asp:Label></h1>

        </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

public partial class Simple_Status : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Request.Params["ID"] != null)

        {

            // check for result

            Guid id = new Guid(Request.Params["ID"]);

           

            ctlProgress.Value = FeedbackProcessCollection.GetResult(id).Progress;

 

            if (!FeedbackProcessCollection.GetResult(id).Complete)

            {

                Response.AddHeader("Refresh", "1");

            }

            else

            {

                FeedbackObject fo = FeedbackProcessCollection.GetResult(id);

                lblComplete.Text = fo.Result1 + " " + fo.Result2;

                lblComplete.Visible = true;

            }

        }

        else

        {

            Response.Redirect("Start.aspx");

        }

    }

}

Now we are able to get any data as a result from an asynchronous process as well as percentage of the process completeness.

Adding Ajax features

Eventually, to make user experience even greater we can use Ajax features to our waiting page. Thanks to Microsoft Ajax Extensions (http://ajax.asp.net/) we do not need to do much work. We will add ScriptManager control and UpdatePanel controls and modify only the waiting page. (Please note, to use Microsoft Ajax Extensoins for ASP.NET you should add a reference to System.Web.Extenstions assembly and configure web.config file for your application. You may look through the sample application configuration file to get familiar with the configuration.)

The waiting page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Status.aspx.cs"
Inherits="Simple_Status" %>

 

<%@ Register TagPrefix="my" Namespace="MyControls" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Feedback Waiting Page</title>

</head>

<body>

    <form id="form1" runat="server">

        <asp:ScriptManager ID="ScriptManager1" runat="server"

EnablePartialRendering="true">

        </asp:ScriptManager>

        <div>

            <asp:UpdatePanel ID="UpdatePanel1" runat="server"
ChildrenAsTriggers="true">

                <ContentTemplate>

                    <asp:Timer ID="Timer1" runat="server" Interval="1000"
OnTick="Timer1_Tick">

                    </asp:Timer>

                    <my:SimpleProgressControl ID="ctlProgress" runat="server"
BackColor="blue" ForeColor="red"

                        Max="100" Value="0" Width="300" Height="30" />

                    <h1>

                        <asp:Label ID="lblComplete" runat="server"
Text="Process complete." Visible="false"></asp:Label></h1>

                </ContentTemplate>

            </asp:UpdatePanel>

        </div>

    </form>

</body>

</html>

 

using System;

using System.Data;

using System.Configuration;

using System.Collections;

using System.Web;

using System.Web.Security;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Web.UI.WebControls.WebParts;

using System.Web.UI.HtmlControls;

 

public partial class Simple_Status : System.Web.UI.Page

{

    protected void Page_Load(object sender, EventArgs e)

    {

        if (Request.Params["ID"] == null)

        {

            Response.Redirect("Start.aspx");

        }

    }

    protected void Timer1_Tick(object sender, EventArgs e)

    {

        // check for result

        Guid id = new Guid(Request.Params["ID"]);

 

        ctlProgress.Value = FeedbackProcessCollection.GetResult(id).Progress;

 

        if (FeedbackProcessCollection.GetResult(id).Complete)

            lblComplete.Visible = true;

    }

}

 

As this is all we have to change to use Ajax features at our waiting page. Now only the small part of the page will be updated during the progress control refreshes.

Conclusion

In this paper we saw how to quickly create a waiting page to use in our applications. If you need only to show to user that a process is still running or already finished, the simplest solution will do for you (note that you also can add Ajax features for better user experience!). If your process should return complex data or may be split in discrete parts you may like to use the waiting page with feedback, or if you have to run a few processes you may use the multi-process version of the waiting page.

To understand this stuff better look through the attached source code.



This article has been viewed 31731 times.
Gaidar Magdanurov

Author profile: Gaidar Magdanurov

Gaidar, an MVP for Visual Basic, lives in Moscow. His primary job is scientific research in INEOS RAS. Gaidar also works as a software developer/consultant and is editor-in-chief of the VBStreets web site, dedicated to Visual Basic and Microsoft .NET technologies. An active member of the Russian GotDotNet community, Gaidar also enjoys speaking at user-group meetings. In his spare time, Gaidar likes playing the guitar, going to the theater, walking in the park and visiting friends.

Search for other articles by Gaidar Magdanurov

Rate this article:   Avg rating: from a total of 69 votes.


Poor

OK

Good

Great

Must read
 
Have Your Say
Do you have an opinion on this article? Then add your comment below:
You must be logged in to post to this forum

Click here to log in.


Subject: What about in Load Balanced scenarios
Posted by: Paul (view profile)
Posted on: Tuesday, July 24, 2007 at 5:42 PM
Message: This is ok if you have one server, or I guess 'sticky sessions', but what if you cannot use sticky sessions....?

Subject: Great article
Posted by: Ralph (view profile)
Posted on: Tuesday, July 24, 2007 at 6:25 PM
Message: Great article, keep up the good work!

Subject: Sychronize access to controller
Posted by: Anonymous (not signed in)
Posted on: Wednesday, July 25, 2007 at 6:07 AM
Message: You should synchronize the access to the controller or to the collection within the controller. If not you will run into issues with accessing the collection from multiple threads.

Subject: Waiting Page
Posted by: Anonymous (not signed in)
Posted on: Wednesday, July 25, 2007 at 6:32 AM
Message: Excellent ! sent more like information to my
mail-id psj@ganini.com

Thank you and continue your service

PSJanarthanan
Project Manager
Ganini Infotech India P Ltd.

Subject: Over-architected?
Posted by: Anonymous (not signed in)
Posted on: Wednesday, July 25, 2007 at 11:56 AM
Message: Unless it's terribly important to know where the user is at in the process (i.e.- the progress bar solution), it's a WHOLE lot easier simply to Post to the "Waiting Page" and have that redirect (or javascript submit) to the final page. Until that final page starts loading, the "Waiting Page" will be displayed in the browser.

Subject: Great idea
Posted by: Hicham (not signed in)
Posted on: Tuesday, November 27, 2007 at 6:58 AM
Message: the SimpleProcessCollection class as a shared resource is a great idea!

Subject: Tremendous Job
Posted by: shabir_hakim1 (view profile)
Posted on: Tuesday, March 10, 2009 at 6:08 AM
Message: No words to praise

 






recommended site pinvoke

PInvoke.net is a user-driven wiki which provides .NET developers with native method signatures, so they don't have to spend time writing them from scratch.




Has .NET Reflector Saved Your Bacon?
 We think Reflector is a fantastic tool, and we know you do too. We'd love to hear about the times... Read more...

The Managed Heap
 Because Red-Gate's .NET team works closely with the users of their products in order to try to fit the... Read more...

Using Three Flavors of LINQ To Populate a TreeView
 LINQ is a valuable technology. LINQ to XML, LINQ to Objects and LINQ to XSD, in particular, can save... Read more...

How to build a Query Template Explorer
 Having introduced his cross-platform Query Template solution, Michael now gives us the technical... Read more...

How to Create Event Receivers for Windows SharePoint Services 3.0
 You'll be surprised how often that you'll use event receivers instead of Workflow in order to implement... Read more...

A Complete URL Rewriting Solution for ASP.NET 2.0
 Ever wondered whether it's possible to create neater URLS, free of bulky Query String parameters?... Read more...

Visual Studio Setup - projects and custom actions
 This article describes the kinds of custom actions that can be used in your Visual Studio setup project. Read more...

.NET Application Architecture: the Data Access Layer
 Find out how to design a robust data access layer for your .NET applications. Read more...

Web Parts in ASP.NET 2.0
 Most Web Parts implementations allow users to create a single portal page where they can personalize... Read more...

Configuring Forms Authentication in SharePoint 2007
 Damon Armstrong provides a step-by-step guide to the processes, quirks and pitfalls of setting up... Read more...

Over 150,000 Microsoft professionals subscribe to the Simple-Talk technical journal. Join today, it's fast, simple, free and secure.

Join Simple Talk