Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

A More Efficient AJAX Progress Bar for ASP.NET

4.90/5 (32 votes)
27 Jan 2016CPOL14 min read 148.3K   5.2K  
AJAX Progress Bar for ASP.NET.
Introduction 

In ASP.NET, when the system executes a long running process, developers are asked to show the status of the process. This article introduces a more efficient Ajax Progress Bar which displays the percent completed or customized message. The control based on Ajax Server Control structure of ASP.NET, uses jQuery (version 1.9.1) as JavaScript library and works on Firefox, Chrome and IE. 

Background  

In the past few years development, I often face to solve the problem that How to display the status of the long running process. As my experience before, I used a timer to make a request to server and get the info back. Although it could solve the problem, the performance is bad. Because asp.net will post all the value of control with runnat="server" setting and view state from front-end to back-end per action, if a page have more than one hundred control in and there are more than 10 concurrent user operate this page, that is a terrible thing, isn't it? Alternative, write JavaScript method to retrieve info of the process using web method in order to improve the performance, but do like that need to write a piece of code to invoke the JavaScript method in code-behind file(i.e. XX.aspx.cs) where you want to use the progress bar, that is low cohesion and maintain not only js code but also server code. All of these issue are caused by asp.net architecture. In order to solve this problem, I research from the MSDN and finally find a tech to solve, that is custom Ajax Server Control. Say something more, the progress bar is implemented by some actual project and won many appreciations.

Feature

  1. More Effective. Use Ajax call to retrieve the info of the process.
  2. Customize interval per call.
  3. Allow more than one progress bar place on the same page and start synchronously.
  4. Customize the display message.
  5. Could add a sever event after process completed.
  6. High cohesion. Embedded JavaScript code and CSS style in the control.
  7. Able to start the progress bar using JS.
  8. Retrieve percent completed by WCF, web service or restful service.  
  9. Allow user to customize a template to display the process info. (2013-10-06) 

Not contained at this moment.

  1. Allow user to customize a template to display the process info. 
  2. Pause the progress bar and then continue it.    

Image 1

Using the code   

First, I must make sure anyone if you want to use my control have a well know of "ASP.NET HTTP handler". If you how to implement the IHttpHandler interface and how it works, you can split this section.
        
"An ASP.NET HTTP handler is the process (frequently referred to as the "endpoint") that runs in response to a request made to an ASP.NET Web application. The most common handler is an ASP.NET page handler that processes .aspx files. When users request an .aspx file, the request is processed by the page through the page handler. You can create your own HTTP handlers that render custom output to the browser."
        
Above statement is defined by MSDN, Please pay attention to the last sentence "You can create your own HTTP handlers that render custom output to the browser." means developer can custom output through extend the HTTP handler and "IHttpHandler" is the interface that we need. 

IHttpHandler Interface. 

There are one property and one method in this interface.

C#
/// Property : IsReusable
public bool IsReusable
{
   // Return false in case your Managed Handler cannot be reused for another request.
   // Usually this would be false in case you have some state information preserved per request.
   get { return true; }
}

/// Method : ProcessRequest
public void ProcessRequest(HttpContext context)
{
   //write your handler implementation here.
}

 

  • <a href="http://msdn.microsoft.com/en-us/library/system.web.ihttphandler.isreusable.aspx">IsReusable</a> : As the comment said. If IsReusable return false, the handler have individual info for each request, otherwise, share the same info to another request. But I have not changed to "false". because I don't trust what the handler stores and I only trust what I send from URL using QueryString.   
  • <a href="http://msdn.microsoft.com/en-us/library/system.web.ihttphandler.processrequest.aspx">ProcessRequest</a> : Describes how to handle the request and response the output. That is what we need to implementation. Let's take a sample to describe how to implement IHttpHandler.  
C#
using System;
using System.Web;
using System.Linq;
using System.Collections.Generic;
 
namespace ABC
{
    public class IISHandler: IHttpHandler
    {
        #region IHttpHandler Members
        public bool IsReusable
        {
            // Return false in case your Managed Handler cannot be reused for another request.
            // Usually this would be false in case you have some state information preserved per request.
            get { return true; }
        }
 
        public void ProcessRequest(HttpContext context)
        {
            //write your handler implementation here.
            string outputName = "Wells";
            List<string> QueryStringKeyList = context.Request.QueryString.AllKeys.ToList();
 
            if (QueryStringKeyList.Contains("name") && !string.IsNullOrWhiteSpace(context.Request.QueryString["name"]))
            {
                outputName = context.Request.QueryString["name"];
            }
 
            HttpResponse Response = context.Response;
            Response.Write("<html>");
            Response.Write("<body>");
            Response.Write("<h1>Hello! " + outputName + ".</h1>");
            Response.Write("</body>");
            Response.Write("</html>");
        }
        #endregion
    }
}

In previous sample, I define a variable named "outputName" and assign "Wells" as default value, and then, if the URL does not have a key named "name" or the value of that is null or empty, the method will response "Hello! Wells." to the browser, otherwise, response "Hello! " + name + "." to the browser.

After the handler implementation, we must register an HTTP handler into the application so that it can capture the request.

Open Web.config file and create an httpHandlers section. Note: Prevent to use reserved words as extension name, such as XXX.aspx, XXX.asmx, XXX.cs, XXX.svc, XXX.xaml, XXX.cshtml and so on. In general, we set XXX.ashx.

C#
<configuration>
    <system.web>
        // IIS 6.0
        <httpHandlers>
            <add verb="*" path="HelloHandle.ashx" type="ABC.IISHandler" />
        </httpHandlers>
    </system.web>
    <system.webServer>
        // IIS 7.0
        <handlers>
            <add name="HelloHandle_ashx" path="HelloHandle.ashx" verb="*" type="ABC.IISHandler"/>
        </handlers>
    </system.webServer>
</configuration>

Finally, we build the project and test it in the browser. Open the browser and type http://XXXX/HelloHandle.ashx. Replace "XXXX" based on your IIS settings.  

Diagram1: URL with no QueryString 


Image 2

 

 

Diagram2: URL with QueryString


Image 3

 

 

 

 

So after the first section, I believe you can create http handler by yourself. If you still can not understand, I suggest you to do some tests. Next section, we will start to learn how to use the progress bar. I will take five samples to describe this topic. As go from the easy to the difficult, we will study the simplest first. 

 

 

Simple Usage:

1. Open VS2010 or VS2012 and create a asp.net web application named whatever you wanted, Here, I set "CoolServerControlTester" as the name. 
        
2. Import the progress bar control into the project. The dll file is placed in the folder named "Lib".
        
3. Create a new web form(.aspx) named "ProgressBarTester.aspx" and also you could rename it.
        
4. Register the control in the page. 

 

ASP.NET
<%@ Register Assembly="CoolServerControl" Namespace="CoolServerControl" TagPrefix="wellsControl" %>

 

5. Define an updatepanel in the page. 

 

C#
<asp:UpdatePanel ID="UP1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
    ...........
    </ContentTemplate>
</asp:UpdatePanel>

6. Add "Process" button and label into the updatepanel.

 

C#
<asp:UpdatePanel ID="UP1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <div style="font: bold 12px arial;">
            Simple Progress Bar:
        </div>
        <div style="float: left; width: 410px;">
            <wellsControl:CoolProgressBar runat="server" ID="SimpleProgressBar" Height="20" Width="400"
                Interval="100" HideProgressBar="false" DefaultEmptyText="Press Process Button To Start The ProgressBar"
                RequestUrl="~/ProgressBarHandler_Simple.ashx" LabelLayout="Left" />
        </div>
        <div style="float: left;">
            <asp:Button runat="server" ID="ProcessBtn" Height="22" Text="Process Button" />
        </div>
        <div style="float: left; font: bold 12px arial; line-height: 22px;" runat="server"
            id="label1">
        </div>
    </ContentTemplate>
</asp:UpdatePanel>

7. Place the progress bar into the UpdatePanel.

C#
<wellsControl:CoolProgressBar runat="server" ID="SimpleProgressBar" Height="20" Width="400" Interval="100" HideProgressBar="false" DefaultEmptyText="Press Process Button To Start The ProgressBar" RequestUrl="~/ProgressBarHandler_Simple.ashx" LabelLayout="Left" />
There some properties here:  
  • Interval: Indicates the interval between each request called by progress bar in milliseconds.
  • HideProgressBar: Specifies whether the progress bar will be show or not when page onload. The default is true, that means do not display the progress bar when page onload.
  • DefaultEmptyText: Specifies the default message in the progress bar.
  • LabelLayout: Determines the message horizontal alignment. "Left","Center" and "Right", The default is "Center".  
  • RequestUrl: Provides a relative url to receive the info from the http handler.

Note: I use an UpdatePanel with property "UpdateMode" is "Conditional" to wrap the progress bar so that only child controls of UpdatePanel will be posted when click the process button. In this scenario, I can trigger the other progress bar(other sample) at the same time and need not post all of the page.

8. Open the code-behind file.

9. Assign event to the process button when press it.

C#
ProcessBtn.Click += ProcessBtn_Click;

void ProcessBtn_Click(object sender, EventArgs e)
{
    SimpleProgressBar.ProgressBarGuid = Guid.NewGuid().ToString();
    label1.InnerText = "";
    SimpleProgressBar.StartProgressBar();
    ProcessBtn.Enabled = false;
}

In the event method, we assign a unique GUID to the progress bar. And then, clear the label. Finally, trigger the progress bar to run.

10. Implement the IHttpHandler interface to receive the call made by progress bar. The details of how to implement the IHttpHandler and how it works mentioned above. You can review the first section or open my sample attached in the article to remind your memory.

C#
public class ProgressBarHandler_Simple : IHttpHandler
{
    #region IHttpHandler Members
    public bool IsReusable
    {
        // Return false in case your Managed Handler cannot be reused for another request.
        // Usually this would be false in case you have some state information preserved per request.
        get { return true; }
    }

    public void ProcessRequest(HttpContext context)
    {
        int processedCount = Convert.ToInt32(context.Request["ProcessedCount"]);
        //Dummy
        int totalCount = 500;
        CoolProgressBarStatus progressBarStatus = new CoolProgressBarStatus(context.Request["ProcessDateTime"].ToString(), context.Request["DatetimeFormat"].ToString(), processedCount + 1, totalCount, Math.Round(((double)processedCount * 100) / totalCount, 2, MidpointRounding.AwayFromZero) + "%");

        context.Response.ContentType = "text/xml";
        if (progressBarStatus != null)
        {
            context.Response.Write(progressBarStatus.Serialize().OuterXml);
        }
        context.Response.Flush();
    }
    #endregion
}

This method is provided to the progress bar to get the latest status of the process from each request. There are some QueryString you can get from the request through URL.

E.g.

~/ProgressBarHandler_Simple.ashx?ProcessDateTime=20131004182640&DatetimeFormat=yyyyMMddHHmmss&ProgressBarGuid=de0fcf4e-83d5-4cd7-9d95-85070c7a4081&Percentage=12.8&RandomKey=42588146267260&ProcessedCount=64&TotalCount=500

 

  • ProcessDatetime : Describes the process start time.
  • DatetimeFormat : Describes the format of date time.
  • ProgressBarGuid : Unique key of the process.
  • Percentage : Current percent completed.
  • RandomKey : Avoid to get the cache of browser.(2013-09-18 edited)
  • ProcessedCount : Processed count. 
  • TotalCount : Total count of the process. 

In previous code segment, I create a instance of CoolProgressBarStatus based on the QueryString value. CoolProgressBarStatus which is embedded in the control is used to group the info of process status and will be serialized to XML format for progress bar used after method ProcessRequest executed, it contains many properties such as total count, processed count,process start time and elapsed time. In order to make it simple, I hard-code total record count to 500 and add 1 to processed count per request.

11. Register the handler in the web.config.

C#
<system.web>
    <compilation debug="true" defaultLanguage="c#" targetFramework="4.0" />
    <httpHandlers>
        <add path="ProgressBarHandler_Simple.ashx" verb="*" type="CoolServerControlTester.ProgressBarHandler_Simple" validate="false" />
    </httpHandlers>
</system.web>

12. Add  a event to the progress bar when process completed.

C#
SimpleProgressBar.OnCompleted += new CoolServerControl.CoolProgressBar.Completed(SimpleProgressBar_OnCompleted);

void SimpleProgressBar_OnCompleted(CoolServerControl.CoolProgressBar coolProgressBar, Guid progressBarGuid)
{
    label1.InnerText = "Process Completed! " + getCompleteMsg(coolProgressBar);
    ProcessBtn.Enabled = true;
}

private string getCompleteMsg(CoolServerControl.CoolProgressBar progressbar)
{
    return getCompleteMsg(progressbar.ProgressBarGuid, progressbar.ProcessStartTime, progressbar.ProcessEndTime, progressbar.TotalCount, progressbar.ProcessedCount);
}

private string getCompleteMsg(string uniqueKey, DateTime startDatetime, DateTime endDatetime, int totalCount, int processedCount)
{
    string reStr = "";
    reStr += " Unique Key : ";
    reStr += uniqueKey.ToString();
    reStr += " Start Time : ";
    if (startDatetime != null && startDatetime != new DateTime())
    {
        reStr += startDatetime.ToString("yyyy-MM-dd hh:mm:ss");
    }
    reStr += " End Time : ";
    if (endDatetime != null && endDatetime != new DateTime())
    {
        reStr += endDatetime.ToString("yyyy-MM-dd hh:mm:ss");
    }
    reStr += " Total Count : ";
    reStr += totalCount.ToString();
    reStr += " Processed Count : ";
    reStr += processedCount.ToString();
    return reStr;
}

 

13. Press F5 to run. 

Image 4

Image 5

In the following sample, we will go into more depth on how to use progress bar work with WCF services. Let us refresh mind and continue. 

Progress Bar Work With WCF Call 

First of all, We will create WCF service project and add some operations in it.  

  1. Navigate "File->Add->New Project..." of the top menu in VS2010.

  2. Select "WCF Service Application" as project template and give a name to the project. 

    Image 6

  3. Delete original service created by project template and add a new wcf service named "ProgressBarSer.svc". 

  4. Add three operations into the service as below. 

    • StartProcess(Guid guid): Start a long running process with unique key.  ( One-way message exchange pattern )
    • GetProcessPrecentage(Guid guid): Get process status using unique key.
    • ProcessComplete(Guid guid): Delete process status from server memory, after the process completed. 
  5. In order to store process status of service, we create a class named "ProcessStatus" and apply DataContract to it.

     

    C#
    [DataContract]
    public class ProcessStatus
    {
        private int _processedCount = 0;
    
        [DataMember(EmitDefaultValue = false)]
        public DateTime StartTime
        {
            get;
            set;
        }
    
        [DataMember(EmitDefaultValue = false)]
        public DateTime EndTime
        {
            get;
            set;
        }
    
        [DataMember(EmitDefaultValue = false)]
        public int TotalCount
        {
            get;
            set;
        }
    
        [DataMember(EmitDefaultValue = false)]
        public int ProcessedCount
        {
            get
            {
                return _processedCount;
            }
            set
            {
                _processedCount = value;
            }
        }
    }
    

     

     

  6. In order to manage all the process's status, we create a static class named "ProcessManager".

     

    C#
    public static class ProcessManager
    {
        private static object sysLock = new object();
    
        public static Dictionary<Guid, ProcessStatus> ProcessList = new Dictionary<Guid, ProcessStatus>();
    
        public static T GetPercentage<T>(Guid guid) where T : ProcessStatus
        {
            T temp = null;
            if (ProcessList.ContainsKey(guid))
            {
                temp = (T)ProcessList[guid];
            }
            return temp;
        }
    
        public static void Process_Start<T>(Guid guid, int totalCount) where T : ProcessStatus
        {
            T processStatus = Activator.CreateInstance<T>();
            lock (sysLock)
            {
                if (ProcessList.ContainsKey(guid))
                {
                    ProcessList.Remove(guid);
                }
                processStatus.StartTime = DateTime.Now;
                processStatus.TotalCount = totalCount;
                ProcessList[guid] = processStatus;
            }
        }
    
        public static void Process_Update<T>(Guid guid, Action<T> action = null) where T : ProcessStatus
        {
            lock (sysLock)
            {
                if (ProcessList.ContainsKey(guid))
                {
                    ((T)ProcessList[guid]).ProcessedCount++;
                    if (action != null)
                    {
                        action(((T)ProcessList[guid]));
                    }
                }
            }
        }
    
        public static void Process_Complete(Guid guid)
        {
            lock (sysLock)
            {
                if (ProcessList.ContainsKey(guid))
                {
                    ProcessList.Remove(guid);
                }
            }
        }
    }
    

     

    In previous code segment, we create a static Dictionary to store all the process status so that we can get the process status by unique key. On the other hand, we create a static object, through lock it to ensure that a block of code runs to completion without interruption by other threads

  7. Implement the three abstract methods in interface. 

     

    C#
    public void StartProcess(Guid guid)
    {
        //Hard-code total count of the process to 200.
        int totalCount = 200;
        //Initial a new process's status.
        ProcessManager.Process_Start<ProcessStatus>(guid, totalCount);
    
        for (int i = 0; i < totalCount; ++i)
        {
            //As demonstration, suspend current thread for 0.1 second.
            Thread.Sleep(100);
            //Update processed count.
            ProcessManager.Process_Update<ProcessStatus>(guid);
        }
    }
    
    public ProcessStatus GetProcessPrecentage(Guid guid)
    {
        ProcessStatus processStatus = ProcessManager.GetPercentage<ProcessStatus>(guid);
        return processStatus;
    }
    
    public void ProcessComplete(Guid guid)
    {
        //Remove process's status by unique key after process completed so that free the memory.
        ProcessManager.Process_Complete(guid);
    } 
    

     

  8. Add service endpoint and service behavior into services section and behaviors for the WCF service. 

    C#
    <services>
        <service behaviorConfiguration="MyServiceBehavior" name="WCFService.ProgressBarSer">
            <endpoint address="" binding="basicHttpBinding" bindingConfiguration=""
                    contract="WCFService.IProgressBarSer" />
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        </service>
    </services>
    <behaviors>
        <serviceBehaviors>
            <behavior name="MyServiceBehavior">
                <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
                    <serviceMetadata httpGetEnabled="true"/>
                <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
                <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
        </serviceBehaviors>
    </behaviors>
    
  9. Return to asp.net project and add service reference mentioned above. 

    Image 7

  10. Create a class that implement IHttpHandler interface named "ProgressBarHandler_WCF" to get process status from WCF service.  

     

    C#
    public class ProgressBarHandler_WCF : IHttpHandler
    {
        #region IHttpHandler Members
        public bool IsReusable
        {
            // Return false in case your Managed Handler cannot be reused for another request.
            // Usually this would be false in case you have some state information preserved per request.
            get { return true; }
        }
    
        public void ProcessRequest(HttpContext context)
        {
            string guid = context.Request["ProgressBarGuid"].ToString();
    
            CoolServerControlTester.ProgressBarSer.ProcessStatus processStatus = null;
    
            using (ProgressBarSerClient progressBarClient = new ProgressBarSerClient())
            {
                processStatus = progressBarClient.GetProcessPrecentage(Guid.Parse(guid));
            }
    
            CoolProgressBarStatus progressBarStatus = new CoolProgressBarStatus(DateTime.Now, 0, 100);
    
            if (processStatus != null)
            {
                progressBarStatus = new CoolProgressBarStatus(context.Request["ProcessDateTime"].ToString(), context.Request["DatetimeFormat"].ToString(), processStatus.ProcessedCount, processStatus.TotalCount);
                progressBarStatus.MsgText = (Math.Round(Convert.ToDouble(processStatus.ProcessedCount) / processStatus.TotalCount * 100, 2, MidpointRounding.AwayFromZero)) + "%(" + processStatus.ProcessedCount + "/" + processStatus.TotalCount + ") " + progressBarStatus.ElapsedTimeStr;
            }
            context.Response.ContentType = "text/xml";
            if (progressBarStatus != null)
            {
                context.Response.Write(progressBarStatus.Serialize().OuterXml);
            }
            context.Response.Flush();
        }
        #endregion
    }
    

     

  11. Register the handler to the application. 

     

    C#
    <system.web>
        <compilation debug="true" defaultLanguage="c#" targetFramework="4.0" />
        <httpHandlers>
            <add path="ProgressBarHandler_Simple.ashx" verb="*" type="CoolServerControlTester.ProgressBarHandler_Simple" validate="false" />
            <add path="ProgressBarHandler_WCF.ashx" verb="*" type="CoolServerControlTester.ProgressBarHandler_WCF" validate="false" />
        </httpHandlers>
    </system.web>
    

     

  12. Insert a new progress bar wrapped by UpdatePanel into the .aspx page as the same as the first sample.   

  13. Add button click event to the button and add completed event to progress bar when page load. 

    C#
    protected void Page_Load(object sender, EventArgs e)
    {
        #region [ Simple Progress Bar ]
        ProcessBtn.Click += ProcessBtn_Click;
        SimpleProgressBar.OnCompleted += new CoolServerControl.CoolProgressBar.Completed(SimpleProgressBar_OnCompleted);
        #endregion
    
        #region [ WCF Progress Bar ]
        ProcessBtn_Wcf.Click += ProcessBtn_Wcf_Click;
        WCFProgressBar.OnCompleted += WCFProgressBar_OnCompleted;
        #endregion
    }
    
    #region [ WCF Progress Bar Event ]
    void WCFProgressBar_OnCompleted(CoolServerControl.CoolProgressBar coolProgressBar, Guid progressBarGuid)
    {
        label2.InnerText = "Process Completed!" + getCompleteMsg(coolProgressBar);
        using (ProgressBarSerClient progressBarClient = new ProgressBarSerClient())
        {
            progressBarClient.ProcessComplete(Guid.Parse(coolProgressBar.ProgressBarGuid));
        }
        ProcessBtn_Wcf.Enabled = true;
    }
    
    void ProcessBtn_Wcf_Click(object sender, EventArgs e)
    {
        WCFProgressBar.ProgressBarGuid = Guid.NewGuid().ToString();
        label2.InnerText = "";
        WCFProgressBar.StartProgressBar();
        using (ProgressBarSerClient progressBarClient = new ProgressBarSerClient())
        {
            progressBarClient.StartProcess(Guid.Parse(WCFProgressBar.ProgressBarGuid));
        }
        ProcessBtn_Wcf.Enabled = false;
    }
    #endregion
    

    In previous code segment, we use proxy class that is auto-generated by Visual Studio to consume the WCF serivces. However, you can also manually define a dynamic proxy class to do that such as  ChannelFactory, which is more flexible than original one, but I don't want to go into more depth of WCF.

  14. Press F5 to run.

    Image 8
     

In previous two samples, I demonstrate the basic usage of the progress bar and I think you have many ideas of about how to use the progress bar in your solution now. But please keep these ideas in your mind and follow me to learn the next sample about how to use JavaScript to trigger the progress bar. I think it very useful when you make a call from JavaScript .

Progress Bar Triggered By Javascript Work With WCF Call  

In following sample, instead of write too many script, I will use jQeury known as js library to perform the presentation.  

  1. Download the jQuery library form here. In my sample, the version no. of jQuery is 1.9.1. It is no problem to use the latest version of that. And then, place the file you have just downloaded into your solution. I used to create a Js folder to store the js and create a sub folder named "common" in it to store the utilities such as js library and common functions.
     
  2. Include the js file on the page.
    C#
    <script language="javascript" src="Js/common/jquery-1.9.1.min.js" type="text/javascript"></script> 
  3. Place the progress bar into the test page as sample 1 and sample 2.
    C#
    <asp:UpdatePanel ID="UpdatePanel2" runat="server" UpdateMode="Conditional">
        <ContentTemplate>
            <div style="font: bold 12px arial;">
                Progress Bar Triggered By Javascript Work With WCF Call :
            </div>
            <div style="float: left; width: 410px;">
                <wellsControl:CoolProgressBar runat="server" ID="JSProgressBar" Height="20" Width="400" Interval="100" HideProgressBar="false" DefaultEmptyText="Press Process Button To Start The ProgressBar"
                    RequestUrl="~/ProgressBarHandler_WCF.ashx" />
            </div>
            <div style="float: left;">
                <input name="jsButton" type="button" id="jsButton" style="height: 22px;" value="Process by Javascript"
                    onclick="CallWcfByJS(this,'JSProgressBar','label3');"/>
            </div>
            <div style="float: left; font: bold 12px arial; line-height: 22px;" runat="server" id="label3">
            </div>
        </ContentTemplate>
    </asp:UpdatePanel>   

    Please pay attention to the highlight, the button is not a server contorl and we add client-side onclick event to it. The detail of event will be mentioned in the following content.

  4. Create a JS function to handle the event.
    C#
    function CallWcfByJS(sender, progressBarID, labelID) {
        var guid = GenGUID();
        $.ajax({
            url: "http://localhost:50066/ProgressBarJsSer.svc/StartProcessJS/" + guid,
            type: "GET",
            dataType: "jsonp",
            crossDomain: true
        });
        $("#" + labelID).text("");
        $("#" + progressBarID)[0].control.set_ProgressBarGuid(guid);
        $("#" + progressBarID)[0].control.OnProgressBarStart();
        $(sender).attr("disabled", "disabled");
        var jsCompleteFun = function () {
            $(sender).removeAttr("disabled");
        }
        $("#" + progressBarID)[0].control.set_JsCompleteEvent(jsCompleteFun);
    } 

    In previous code segment, we create a unique key using predefined function. And then, we make a Ajax call to a restful service of WCF to start a new process. $.ajax is an AJAX method in jQuery and perform a asynchronous call by default, so the system need not wait until the response return. And then, clear message of the label, disable the button, set unique key to progress bar and trigger it to run. At last, define a client side completed event to the progress bar, which will be executed after the progress bar completed.

  5.  Add a event to the progress bar when process completed in code-behind file.
    C#
    protected void Page_Load(object sender, EventArgs e)
    {
        #region [ Simple Progress Bar ]
        ProcessBtn.Click += ProcessBtn_Click;
        SimpleProgressBar.OnCompleted += new CoolServerControl.CoolProgressBar.Completed(SimpleProgressBar_OnCompleted);
        #endregion
     
        #region [ WCF Progress Bar ]
        ProcessBtn_Wcf.Click += ProcessBtn_Wcf_Click;
        WCFProgressBar.OnCompleted += WCFProgressBar_OnCompleted;
        #endregion
     
        #region [ Js Progress Bar ]
        JSProgressBar.OnCompleted += JSProgressBar_OnCompleted;
        #endregion
    }
     
    #region [ JS Progress Bar Event ]
    void JSProgressBar_OnCompleted(CoolServerControl.CoolProgressBar coolProgressBar, Guid progressBarGuid)
    {
        label3.InnerText = "Process Completed!" + getCompleteMsg(coolProgressBar);
        using (ProgressBarSerClient progressBarClient = new ProgressBarSerClient())
        {
            progressBarClient.ProcessComplete(Guid.Parse(coolProgressBar.ProgressBarGuid));
        }
    }
    #endregion 
  6. Navigate to WCF service project and add a restful operation with one-way message exchange pattern.
    C#
    [ServiceContract]
    public interface IProgressBarJsSer
    {
        [OperationContract(IsOneWay = true)]
        [WebGet(UriTemplate = "StartProcessJS/{guidStr}")]
        void StartProcess_JS(string guidStr);
    } 
  7. Implement the interface.
    C#
    public class ProgressBarJsSer : IProgressBarJsSer
    {
        public void StartProcess_JS(string guidStr)
        {
            Guid guid = Guid.Parse(guidStr);
            int totalCount = 100;
            ProcessManager.Process_Start<ProcessStatus>(guid, totalCount);
    
            for (int i = 0; i < totalCount; ++i)
            {
                Thread.Sleep(100);
                ProcessManager.Process_Update<ProcessStatus>(guid);
            }
        }
    } 
  8. Config the restful service in web.config.
    C#
    <services>
        <service behaviorConfiguration="MyServiceBehavior" name="WCFService.ProgressBarJsSer">
            <endpoint address="" behaviorConfiguration="WebBehavior" binding="webHttpBinding"
                contract="WCFService.IProgressBarJsSer" />
            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        </service>
    </services>
     
    <behaviors>
        <serviceBehaviors>
            <behavior name="MyServiceBehavior">
                <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
                <serviceMetadata httpGetEnabled="true"/>
                <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
                <serviceDebug includeExceptionDetailInFaults="false"/>
            </behavior>
        </serviceBehaviors>
        <endpointBehaviors>
            <behavior name="WebBehavior">
                <webHttp/>
            </behavior>
        </endpointBehaviors>
    </behaviors> 
  9. Press "F5" to run.
    Image 9

Progress Bar With Customizable Info Work With WCF call 

In the following sample, I will introduce how to customize process info based on template.

 

  1. Add a Progress Bar into the test page and design a template to show the process info.

     

    C#
    ......
        <wellsControl:CoolProgressBar runat="server" ID="WCFProgressBarCT" Height="20" Visible="true" Width="400" Interval="100" HideProgressBar="false" DefaultEmptyText="Press Process Button To Start The ProgressBar" RequestUrl="~/ProgressBarHandler_WCF_CT.ashx">
            <CustomizableTemplate>
                <div class="w-panel" style="width: 350px;">
                    <div class="w-panel-header">
                        Process Information
                    </div>
                    <div class="w-panel-body">
                        <div id="r1" class="w-panel-body-row">
                            <div class="w-panel-body-row-left">
                                Sequence No.
                            </div>
                            <div class="w-panel-body-row-right">
                                @SeqNo
                            </div>
                            <div style="clear: both;">
                            </div>
                        </div>
                        <div id="r2" class="w-panel-body-row-alt">
                            <div class="w-panel-body-row-left">
                                Create Time
                            </div>
                            <div class="w-panel-body-row-right">
                                @CreateTime
                            </div>
                            <div style="clear: both;">
                            </div>
                        </div>
                        <div id="r3" class="w-panel-body-row">
                            <div class="w-panel-body-row-left">
                                Amount
                            </div>
                            <div class="w-panel-body-row-right">
                                @Amount
                            </div>
                            <div style="clear: both;">
                            </div>
                        </div>
                        <div id="r4" class="w-panel-body-row-alt">
                            <div class="w-panel-body-row-left">
                                Total Price
                            </div>
                            <div class="w-panel-body-row-right">
                                @TotalPrice
                            </div>
                            <div style="clear: both;">
                            </div>
                        </div>
                    </div>
                </div>
            </CustomizableTemplate>
        </wellsControl:CoolProgressBar>
    ...... 
    

     

    In previous code segment, we design a grid to show process info. Actually, You can design your customizable layout in another page first, then place it into the tag named "CustomizableTemplate". Which word with prefix "@" will be replaced by the data of process info. Here, we declare @SeqNo, @CreateTime, @Amount and @TotalPrice. Next step, we create a class to store these date in the service side. 

  2. Create a DataContract to store the customizable info that will be returned to progress bar.

     

    C#
    [DataContract]
    public class OrderInfo
    {
        private const string datetimeFormat = "yyyy-MM-dd HH:mm:ss";
        private string seqNo = "";
        private DateTime? createTime = null;
        private double totalPrice = 0;
        private int amount = 0;
    
        public OrderInfo(string seqNo, DateTime createTime, double totalPrice, int amount)
        {
            this.seqNo = seqNo;
            this.createTime = createTime;
            this.totalPrice = totalPrice;
            this.amount = amount;
        }
    
        [DataMember]
        public string SeqNo
        {
            get
            {
                return seqNo;
            }
            private set
            {
                seqNo = value;
            }
        }
    
        [DataMember]
        public string CreateTime
        {
            get
            {
                return createTime.HasValue ? createTime.Value.ToString(datetimeFormat) : "";
            }
            private set
            {
                DateTime dReturn;
    
                if (!DateTime.TryParseExact(value, datetimeFormat, null, DateTimeStyles.None, out dReturn))
                {
                }
                createTime = dReturn;
            }
        }
    
        [DataMember]
        public string TotalPrice
        {
            get
            {
                return totalPrice.ToString();
            }
            private set
            {
                totalPrice = Convert.ToDouble(value);
            }
        }
    
        [DataMember]
        public string Amount
        {
            get
            {
                return amount.ToString();
            }
            private set
            {
                amount = Convert.ToInt32(value);
            }
        }
    } 

     

    In previous code segment, we create a class named "OrderInfo", and there are four properties in it. All of these properties type are String.  Because it is hard to automatically convert an object to specific format in client-side and I had to convert the data to specific format in service-side before send back to client.  

  3. Create a class named ProcessStatus_Order that inherits from ProcessStatus and wraps OrderInfo into it so that whoever consume the service can retrieve not only the basic info of the process but also the customizable info from it.  

     

    C#
    [DataContract]
    [KnownType(typeof(OrderInfo))]
    public class ProcessStatus_Order : ProcessStatus
    {
        [DataMember]
        public OrderInfo orderInfo
        {
            get;
            set;
        }
    } 

     

  4. Add three operations into the services contract in order to demonstrate how to start a process with customizable info, get process info and delete the process info from the server memory after process completed. 
    C#
    public void StartProcess_CustomizableInfo(Guid guid)
    {
        //Dummy Data
        List<OrderInfo> orderStatusList = new List<OrderInfo>();
        orderStatusList.Add(new OrderInfo("20130101102", new DateTime(2013, 1, 1), 1001.01, 1));
        ........
        orderStatusList.Add(new OrderInfo("20130111571", new DateTime(2013, 1, 11), 1011.01, 11));
    
        ProcessManager.Process_Start<ProcessStatus_Order>(guid, orderStatusList.Count);
        for (int i = 0; i < orderStatusList.Count; ++i)
        {
            Thread.Sleep(500);
            ProcessManager.Process_Update<ProcessStatus_Order>(guid, (orderStatus) =>
            {
                orderStatus.orderInfo = orderStatusList[i];
            });
        }
    }
    
    public ProcessStatus_Order GetProcessPrecentage_CustomizableInfo(Guid guid)
    {
        ProcessStatus_Order processStatus = ProcessManager.GetPercentage<ProcessStatus_Order>(guid);
        return processStatus;
    }
    
    public void ProcessComplete_CustomizableInfo(Guid guid)
    {
        ProcessManager.Process_Complete(guid);
    } 

    Please pay attention to the highlight code, we use ProcessStatus_Order instead of ProcessStatus.  

  5. Create a new http hander to retrieve the process status and customizable info form WCF service, then assign the process status and customizable info to the progress bar.  In below code segment, we serialize customizable info (i.e. OrderInfo) to json format through JavaScriptSerializer and assign it to MagInJson property of  the progressBarStatus. 
    C#
    public class ProgressBarHandler_WCF_CT : IHttpHandler
    {
        #region IHttpHandler Members
    
        public bool IsReusable
        {
            // Return false in case your Managed Handler cannot be reused for another request.
            // Usually this would be false in case you have some state information preserved per request.
            get { return true; }
        }
    
        public void ProcessRequest(HttpContext context)
        {
            string guid = context.Request["ProgressBarGuid"].ToString();
    
            CoolServerControlTester.ProgressBarSer.ProcessStatus_Order processStatus = null;
    
            using (ProgressBarSerClient progressBarClient = new ProgressBarSerClient())
            {
                processStatus = progressBarClient.GetProcessPrecentage_CustomizableInfo(Guid.Parse(guid));
            }
    
            CoolProgressBarStatus progressBarStatus = new CoolProgressBarStatus(DateTime.Now, 0, 100);
    
            if (processStatus != null)
            {
                progressBarStatus = new CoolProgressBarStatus(context.Request["ProcessDateTime"].ToString(), context.Request["DatetimeFormat"].ToString(), processStatus.ProcessedCount, processStatus.TotalCount);
                progressBarStatus.MsgText = (Math.Round(Convert.ToDouble(processStatus.ProcessedCount) / processStatus.TotalCount * 100, 2, MidpointRounding.AwayFromZero)) + "%(" + processStatus.ProcessedCount + "/" + processStatus.TotalCount + ") " + progressBarStatus.ElapsedTimeStr;
    
                progressBarStatus.MsgInJson = new JavaScriptSerializer().Serialize(processStatus.orderInfo) ?? "";
            }
            context.Response.ContentType = "text/xml";
            if (progressBarStatus != null)
            {
                context.Response.Write(progressBarStatus.Serialize().OuterXml);
            }
            context.Response.Flush();
        }
    
        #endregion
    
  6. Register the handler to the application.  For details info please review above section. 
  7. Add button click event to the button and add completed event to progress bar when page load. 
  8. Press "F5" to run. 

    Image 10

 

Progress Bar With Customizable Info Triggered by Javascript Work With WCF call

The following sample is similar to previous one. But it triggered by Javascript. 

 

Image 11 

  1. Add a customizable template into the progress bar and assign client event to the button.
    C#
    ......
    <wellsControl:CoolProgressBar runat="server" ID="JSProgressBarCT" Height="20" Visible="true"
        Width="400" Interval="100" HideProgressBar="false" DefaultEmptyText="Press Process Button To Start The ProgressBar"
        RequestUrl="~/ProgressBarHandler_TrialRun.ashx">
        <CustomizableTemplate>
            <div class="w-panel" style="width: 350px;">
                <div class="w-panel-header">
                    Process Information
                </div>
                <div class="w-panel-body">
                    <div id="r1" class="w-panel-body-row">
                        <div class="w-panel-body-row-left">
                            Employee No.
                        </div>
                        <div class="w-panel-body-row-right">
                            @EmpNo
                        </div>
                        <div style="clear: both;">
                        </div>
                    </div>
                    <div id="r2" class="w-panel-body-row-alt">
                        <div class="w-panel-body-row-left">
                            Last Hire Date
                        </div>
                        <div class="w-panel-body-row-right">
                            @LastHireDate
                        </div>
                        <div style="clear: both;">
                        </div>
                    </div>
                    <div id="r3" class="w-panel-body-row">
                        <div class="w-panel-body-row-left">
                            Salary
                        </div>
                        <div class="w-panel-body-row-right">
                            @Salary
                        </div>
                        <div style="clear: both;">
                        </div>
                    </div>
                </div>
            </div>
        </CustomizableTemplate>
    </wellsControl:CoolProgressBar> 
    ......
    <input type="button" id="jsButtonCT" value="Process Button"
        runat="server" onclick="CallWcfByJSWithCT(this, 'JSProgressBarCT', 'label5');" />   

    Here, I add three variables. There are @EmpNo,@LastHireDate and @Salary. Then attach a click event to the button.  

  2. Supplement the detail of the js function which start a process through restful service.
    C#
    function CallWcfByJSWithCT(sender, progressBarID, labelID) {
        var guid = GenGUID();
        $.ajax({
            url: "http://localhost:50066/ProgressBarJsSer.svc/TrialRun/" + guid,
            type: "GET",
            dataType: "jsonp",
            crossDomain: true
        });
        $("#" + labelID).text("");
        $("#" + progressBarID)[0].control.set_ProgressBarGuid(guid);
        $("#" + progressBarID)[0].control.OnProgressBarStart();
        $(sender).attr("disabled", "disabled");
        var jsCompleteFun = function () {
            $(sender).removeAttr("disabled");
        }
        $("#" + progressBarID)[0].control.set_JsCompleteEvent(jsCompleteFun);
    } 
  3. Create a DataContract to store the customizable info. 
    C#
    [DataContract]
    public class EmployeeInfo
    {
        private const string datetimeFormat = "dd/MM/yyyy";
        private string empNo = "";
        private DateTime? lastHireDate = null;
        private double salary = 0;
    
        public EmployeeInfo(string empNo, DateTime lastHireDate, double salary)
        {
            this.empNo = empNo;
            this.lastHireDate = lastHireDate;
            this.salary = salary;
        }
    
        [DataMember]
        public string EmpNo
        {
            get
            {
                return empNo;
            }
            private set
            {
                empNo = value;
            }
        }
    
        [DataMember]
        public string LastHireDate
        {
            get
            {
                return lastHireDate.HasValue ? lastHireDate.Value.ToString(datetimeFormat) : "";
            }
            private set
            {
                DateTime dReturn;
    
                if (!DateTime.TryParseExact(value, datetimeFormat, null, DateTimeStyles.None, out dReturn))
                {
                }
                lastHireDate = dReturn;
            }
        }
    
        [DataMember]
        public string Salary
        {
            get
            {
                return "$ " + salary.ToString();
            }
            private set
            {
                salary = Convert.ToDouble(value);
            }
        }
    } 
  4. Create a class to wrap the Employee Info that inherits form ProcessStatus.
    C#
    [DataContract]
    [KnownType(typeof(EmployeeInfo))]
    public class ProcessStatus_Employee : ProcessStatus
    {
        [DataMember]
        public EmployeeInfo employeeInfo
        {
            get;
            set;
        }
    } 
  5. Create a http handler to retrieve the customizable info. (Mentioned before.) 
  6. Register the handler to the application.  
  7. Add completed event to progress bar when page load.
    C#
        .......
        #region [ JS Progress Bar with Customizable Template ]
        JSProgressBarCT.OnCompleted += new CoolServerControl.CoolProgressBar.Completed(JSProgressBarCT_OnCompleted);
        #endregion
    }
    
    #region [ JS Progress Bar with Customizable Template Event ]
    void JSProgressBarCT_OnCompleted(CoolServerControl.CoolProgressBar coolProgressBar, Guid progressBarGuid)
    {
        label5.InnerText = "Process Complete!" + getCompleteMsg(coolProgressBar);
        using (ProgressBarSerClient progressBarClient = new ProgressBarSerClient())
        {
            progressBarClient.TrialRunComplete(coolProgressBar.ProgressBarGuid);
        }
    }
    #endregion   
  8. Press "F5" to run.

At this point, I had introduced five basic usages of the progress bar, you can download my sample project in the attachment. I hope this control can help you to improve the user experience and hope you can discover the other usage based on your requirement. And also, I will continue to refine this control, there are some features as mentioned above I want to add into the control, so I hope you could follow me and bookmark this article to get more in the feature. Thank you for your reading and if you have any questions please feel free to contact me. Thank you! 

History   

  • 2013-09-07 First version released.
  • 2013-09-18 Fixed cache issue. Append random number as parameter to the relative URL(i.e. ) so that it doesn't get cached. Thanks Marco!
  • 2013-10-06 Customize a template to display the process info. 
  • 2016-01-27 Fix display issues. Fullfill latest browser included IE 11, Chrome, Edge.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)