Thursday, September 30, 2010

How to show the progress of long running operation - Detailed Example

Demo:

Showing progress of a long running operation and keep screen up to date with what is happen in the server side is one common tasks one of a common task in web development. In this scenario, I have used UpdatePanel to wrap the bit I need to update and then a separate thread as the worker. Simply in the button click event of the Do Task button, I start a thread and hand over the long running task to the thread execution
Thread thread = new Thread(new ThreadStart(SomeLongOperation));
thread.Start();
 And also I set the progress bar visible true and set up initial message. As well as I have couple of flags in the Session to tell that the state of the page. They are Processing and Completed. I set Processing  flag to true and Completed flag to false and register start-up script to refresh the page in a while.
this.lblMessage.Text = "<br />Processing... Please wait...";
this.pnlProgress.Width = new Unit(this.Step * this.blockWidth);
this.pnlProgress.Visible = true;
this.Processing = true;
ScriptManager.RegisterStartupScript(thisthis.GetType(),
    this.GetType().Name, string.Format(script, this.UpdatePanel1.UniqueID), true);
Then in the next page load, because Processing flag is true I can keep updating the progress bar and status messages until Completed flag toggle back to true.
if (this.Processing)
{
    if (!string.IsNullOrEmpty(this.LastStatus))
    {
       this.pnlProgress.Width = new Unit(this.Step * this.blockWidth);
       this.lblMessage.Text += string.Format("<br />{0}"this.LastStatus);
       this.LastStatus = string.Empty;
    }                
    ScriptManager.RegisterStartupScript(thisthis.GetType(),
       this.GetType().Name, string.Format(script, this.UpdatePanel1.UniqueID),
       true);
}
else
{
    if (!this.Completed)
    {
       lblMessage.Text += "<br />Processing is complete";
       this.Completed = true;
    }
    else
    {
       this.lblMessage.Text = string.Empty;
       this.Step = 0;
    }
}

Complete Example:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Threading" %>
<html>
<head runat="server">
    <script runat="server">
        private string script = @"setTimeout(""__doPostBack('{0}','')"", 3000);";
        protected int totalSteps = 7;
        private int blockWidth = 50;
        private string styles = @"
            <style type=""text/css"">
                .Hide {{ display:none; }}
                .ProgressWrapper {{ height:22px; width:{0}px; 
                        border:solid 1px #9C9C9C; padding:5px; background-color:#ddd; }}
                .Progress {{ height:20px; background-color:#0059FF; 
                        border:solid 1px #003EB0; float:left; }}
            </style>
        ";
        public bool Processing
        {
            get { return (bool)(Session["Processing"] ?? false); }
            set { Session["Processing"] = value; }
        }
        public int Step
        {
            get { return (int)(Session["Step"] ?? 0); }
            set { Session["Step"] = value; }
        }        
        public string LastStatus
        {
            get { return (string)(Session["LastStatus"] ?? string.Empty); }
            set { Session["LastStatus"] = value; }
        }
        public bool Completed
        {
            get
            {
                return (bool)(Session["Completed"] ??
                    (Session["Completed"] = true));
            }
            set { Session["Completed"] = value; }
        }
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            if (this.Processing)
            {
                if (!string.IsNullOrEmpty(this.LastStatus))
                {
                    this.pnlProgress.Width = new Unit(this.Step * this.blockWidth);
                    this.lblMessage.Text += string.Format("<br />{0}"this.LastStatus);
                    this.LastStatus = string.Empty;
                }                
                ScriptManager.RegisterStartupScript(thisthis.GetType(),
                    this.GetType().Name, string.Format(script, this.UpdatePanel1.UniqueID),
                    true);
            }
            else
            {
                if (!this.Completed)
                {
                    lblMessage.Text += "<br />Processing is complete";
                    this.Completed = true;
                }
                else
                {
                    this.lblMessage.Text = string.Empty;
                    this.Step = 0;
                }
            }
            if (this.Page.Header != null)
                this.Page.Header.Controls.Add(
                    new Literal() { Text = string.Format(this.styles, 
                        this.totalSteps * this.blockWidth + 2) });
        }
        protected void Button1_Click(object sender, EventArgs e)
        {
            Thread thread = new Thread(new ThreadStart(SomeLongOperation));
            thread.Start();
            this.lblMessage.Text = "<br />Processing... Please wait...";
            this.pnlProgress.Width = new Unit(this.Step * this.blockWidth);
            this.pnlProgress.Visible = true;
            this.Processing = true;
            ScriptManager.RegisterStartupScript(thisthis.GetType(),
                this.GetType().Name, string.Format(script, this.UpdatePanel1.UniqueID), true);
        }
        void SomeLongOperation()
        {
            this.Completed = false;
            ///
            /// this is just to demonstrate
            /// should you have your own logic there
            /// and change the last status accordingly
            /// 
            for (int i = 0; i < 25; i++)
            {
                if (i % 4 == 0)
                {
                    this.Step = this.Step + 1;
                    string status = string.Format("Step {0} - {1}", i / 4 + 1,
                        DateTime.Now.Ticks % 2 == 0 ? "Success" : "Faild - Due to {exception details}");
                    if (string.IsNullOrEmpty(this.LastStatus))
                        this.LastStatus = status;
                    else this.LastStatus = string.Format("{0}<br />{1}"this.LastStatus, status);
                }
                Thread.Sleep(1000);
            }
            this.Processing = false;
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="pageScriptManager" />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <asp:Button runat="server" ID="btnDoTask" Text="Do Task" OnClick="Button1_Click" />
            <hr />
            <asp:Panel runat="server" ID="pnlProgressWrapper" CssClass="ProgressWrapper">
                <asp:Panel runat="server" ID="pnlProgress" CssClass="Progress" Visible="false" />
            </asp:Panel>
            <hr />
            <asp:Label runat="server" ID="lblMessage" />
        </ContentTemplate>
    </asp:UpdatePanel>
    </form>
</body>
</html>

2 comments:

ase said...

Hi I have seen your great article and trying to apply it there ocurred a problem: I have a class in App_Code that does all the database work and its variables (connection, command, userdata, etc..) are stored in a Session accesor like this:

public static class SessionActual
{
public static HttpSessionState Session
{
get
{
return HttpContext.Current.Session;
}
}
public static string username
{
get
{
return Session["username"].ToString();
}
set
{
Session["username"] = value;
}
}
}

The problem is that inside a thread HttpContext.Current is null so everything fails...
How would you solve it? (Preferably without changing all the system :S)

Thanks a lot!

Charith Shyam Gunasekara said...

You can do something like this

public partial class Test : Page
{
protected void Button1_Click(object sender, EventArgs e)
{
ThreadStart operation = delegate { SomeLongOperation(HttpContext.Current); };
Thread thread = new Thread(new ThreadStart(operation));
thread.Start();
///
/// other stuff
///
}
void SomeLongOperation(HttpContext context)
{
SessionActual.SetUserName(context,"Admin");
string userName = SessionActual.GetUserName(context);
}
}

public static class SessionActual
{
///
/// Other stuff
///
public static string GetUserName(HttpContext context)
{
return (string)context.Session["username"];
}
public static void SetUserName(HttpContext context, string userName)
{
context.Session["username"] = userName;
}
}

iPhone Launch Screen Sizes

iPhone Portrait iOS 8 Retina HT 5.5 = 1242 X 2208 Retna HD 4.7 = 750 X 1134 iPhone Landscape iOS 8 Retina HD 5.5  2208 X 1242 iPho...