65.9K
CodeProject 正在变化。 阅读更多。
Home

用于 ASP.NET 的更高效的 AJAX 进度条

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (31投票s)

2013 年 9 月 6 日

CPOL

14分钟阅读

viewsIcon

150303

downloadIcon

5237

用于 ASP.NET 的 AJAX 进度条。

介绍 

在 ASP.NET 中,当系统执行一个长时间运行的进程时,开发人员需要显示该进程的状态。本文介绍了一种更高效的 Ajax 进度条,它可以显示完成百分比或自定义消息。该控件基于 ASP.NET 的 Ajax 服务器控件结构,使用 jQuery (版本 1.9.1) 作为 JavaScript 库,并且可以在 Firefox、Chrome 和 IE 上运行。

背景  

在过去几年的开发中,我经常面临如何显示长时间运行进程状态的问题。根据我之前的经验,我使用计时器向服务器发出请求并获取信息。虽然可以解决问题,但性能很差。因为 asp.net 会在每次操作时将带有 runnat="server" 设置的所有控件值和视图状态从前端发送到后端,如果一个页面有超过一百个控件,并且有超过 10 个并发用户操作该页面,那将是一件可怕的事情,不是吗?或者,编写 JavaScript 方法使用 Web 方法检索进程信息以提高性能,但这样做需要在代码隐藏文件(即 XX.aspx.cs)中编写代码来调用 JavaScript 方法,这会导致低内聚,不仅需要维护 js 代码,还需要维护服务器代码。所有这些问题都源于 asp.net 架构。为了解决这个问题,我研究了 MSDN,并最终找到了一个解决方案,那就是自定义 Ajax 服务器控件。更具体地说,这个进度条是通过一些实际项目实现的,并获得了许多好评。

功能

  1. 更有效。使用 Ajax 调用检索进程信息。
  2. 自定义每次调用的间隔。
  3. 允许在同一页面上放置多个进度条并同步启动。
  4. 自定义显示消息。
  5. 进程完成后可以添加服务器事件。
  6. 高内聚。将 JavaScript 代码和 CSS 样式嵌入到控件中。
  7. 能够使用 JS 启动进度条。
  8. 通过 WCF、Web 服务或 RESTful 服务检索完成百分比。
  9. 允许用户自定义模板以显示进程信息。(2013-10-06)

目前不包含。

  1. 允许用户自定义模板以显示进程信息。 
  2. 暂停进度条,然后继续。

使用代码

首先,我必须确保任何想要使用我的控件的人都对“ASP.NET HTTP 处理程序”有很好的了解。如果您知道如何实现 IHttpHandler 接口及其工作原理,您可以分开这一部分。
        
“ASP.NET HTTP 处理程序是在响应 ASP.NET Web 应用程序的请求时运行的进程(通常称为“终结点”)。最常见的处理程序是 ASP.NET 页面处理程序,它处理 .aspx 文件。当用户请求 .aspx 文件时,请求将由页面处理程序通过页面进行处理。您可以创建自己的 HTTP 处理程序,以向浏览器呈现自定义输出。”
        
以上陈述由 MSDN 定义,请注意最后一句“您可以创建自己的 HTTP 处理程序,以向浏览器呈现自定义输出。”这意味着开发人员可以通过扩展 HTTP 处理程序来自定义输出,而“IHttpHandler”是我们需要的接口。

IHttpHandler 接口。

此接口包含一个属性和一个方法。

         /// 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.
         }  

 

  • IsReusable:正如注释所述。如果 IsReusable 返回 false,则处理程序为每个请求具有独立信息,否则,与其他请求共享相同信息。但我没有改为“false”。因为我不信任处理程序存储的内容,只信任我通过 QueryString 从 URL 发送的内容。
  • ProcessRequest:描述如何处理请求并响应输出。这是我们需要实现的。让我们通过一个示例来描述如何实现 IHttpHandler
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
    }
}

在之前的示例中,我定义了一个名为“outputName”的变量并将其赋值为“Wells”,然后,如果 URL 中没有名为“name”的键,或者该键的值为空或为 null,则该方法将向浏览器响应“Hello! Wells.”;否则,将向浏览器响应“Hello! ” + name + “.”。

处理程序实现后,我们必须将 HTTP 处理程序注册到应用程序中,以便它可以捕获请求。

打开 Web.config 文件并创建一个 httpHandlers 部分。注意:请勿使用保留字作为扩展名,例如 XXX.aspx, XXX.asmx, XXX.cs, XXX.svc, XXX.xaml, XXX.cshtml 等。通常,我们设置为 XXX.ashx。

    <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> 

最后,我们生成项目并在浏览器中进行测试。打开浏览器并输入 http://XXXX/HelloHandle.ashx。根据您的 IIS 设置替换“XXXX”。

图 1:无查询字符串的 URL


 

 

图 2:带查询字符串的 URL


 

 

 

 

所以,在第一部分之后,我相信您可以自己创建 http 处理程序。如果您仍然不理解,我建议您做一些测试。下一部分,我们将开始学习如何使用进度条。我将用五个示例来描述这个主题。从易到难,我们将首先研究最简单的。

 

 

简单用法

1. 打开 VS2010 或 VS2012,创建一个 asp.net Web 应用程序,名称随意。这里我设置为“CoolServerControlTester”。
        
2. 将进度条控件导入项目中。dll 文件位于名为“Lib”的文件夹中。
        
3. 创建一个新的 Web 窗体 (.aspx),名为“ProgressBarTester.aspx”,您也可以重命名它。
        
4. 在页面中注册控件。

 

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

 

5. 在页面中定义一个 updatepanel。

 

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

6. 将“Process”按钮和标签添加到 updatepanel 中。

 

    <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. 将进度条放置在 UpdatePanel 中。

    <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" />
 
这里有一些属性:
  • Interval:表示进度条每次请求调用的间隔(毫秒)。
  • HideProgressBar:指定页面加载时是否显示进度条。默认值为 true,表示页面加载时不显示进度条。
  • DefaultEmptyText:指定进度条中的默认消息。
  • LabelLayout:确定消息的水平对齐方式。“Left”、“Center”和“Right”,默认为“Center”。
  • RequestUrl:提供一个相对 URL 来接收来自 HTTP 处理程序的信息。

注意:我使用了一个 UpdatePanel,其属性“UpdateMode”为“Conditional”,将进度条包裹起来,这样在点击 Process 按钮时只会发布 UpdatePanel 的子控件。在这种情况下,我可以同时触发另一个进度条(其他示例),而无需发布整个页面。

8. 打开代码隐藏文件。

9. 在按下 Process 按钮时为其分配事件。

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

在事件方法中,我们为进度条分配一个唯一的 GUID。然后,清除标签。最后,触发进度条运行。

10. 实现 IHttpHandler 接口以接收进度条发出的调用。关于如何实现 IHttpHandler 及其工作原理的详细信息已在前面提到。您可以回顾第一部分或打开文章中附加的示例来提醒您的记忆。

    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
    } 

此方法提供给进度条,用于从每次请求获取进程的最新状态。您可以通过 URL 从请求中获取一些 QueryString。

例如:

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

 

  • ProcessDatetime:描述进程开始时间。
  • DatetimeFormat:描述日期时间格式。
  • ProgressBarGuid:进程的唯一密钥。
  • Percentage:当前完成百分比。
  • RandomKey:避免获取浏览器缓存。(2013-09-18 编辑)
  • ProcessedCount:已处理计数。
  • TotalCount:进程的总计数。

在之前的代码段中,我基于 QueryString 值创建了一个 CoolProgressBarStatus 实例。CoolProgressBarStatus 嵌入在控件中,用于分组进程状态信息,并在 ProcessRequest 执行方法后序列化为 XML 格式供进度条使用,它包含总计数、已处理计数、进程开始时间、已用时间等许多属性。为了简单起见,我硬编码了总记录数为 500,并且每次请求加 1。

11. 在 web.config 中注册处理程序。

    <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. 为进度条添加进程完成时触发的事件。

    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. 按 F5 运行。

在下面的示例中,我们将深入探讨进度条与 WCF 服务如何协同工作。让我们回顾一下,继续。

进度条与 WCF 调用协同工作

首先,我们将创建 WCF 服务项目并在其中添加一些操作。

  1. 在 VS2010 的顶部菜单中导航到“文件 -> 添加 -> 新建项目…”。

  2. 选择“WCF 服务应用程序”作为项目模板并为项目命名。

  3. 删除项目模板创建的原始服务,并添加一个名为“ProgressBarSer.svc”的新 wcf 服务。

  4. 在服务中添加三个操作,如下所示。

    • StartProcess(Guid guid):使用唯一密钥启动一个长时间运行的进程。(单向消息交换模式)
    • GetProcessPrecentage(Guid guid):使用唯一密钥获取进程状态。
    • ProcessComplete(Guid guid):进程完成后,从服务器内存中删除进程状态。
  5. 为了存储服务的进程状态,我们创建了一个名为“ProcessStatus”的类,并应用了 DataContract

     

        [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. 为了管理所有进程的状态,我们创建了一个名为“ProcessManager”的静态类。

     

        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);
                    }
                }
            }
        } 

     

    在之前的代码段中,我们创建了一个静态 Dictionary 来存储所有进程状态,以便我们可以通过唯一密钥获取进程状态。另一方面,我们创建了一个静态对象,通过锁定它来确保一段代码能够完整运行,而不会被其他线程中断。

  7. 实现接口中的三个抽象方法。

     

            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. 在 services 部分添加服务终结点和服务行为,以及 WCF 服务的行为。

        <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. 返回到 asp.net 项目并添加上面提到的服务引用。

  10. 创建一个实现 IHttpHandler 接口的类,命名为“ProgressBarHandler_WCF”,以从 WCF 服务获取进程状态。

     

        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. 将处理程序注册到应用程序。

     

        <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. 与第一个示例相同,在 .aspx 页面中插入一个由 UpdatePanel 包裹的新进度条。

  13. 为按钮添加点击事件,并在页面加载时为进度条添加完成事件。

        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
    

    在之前的代码段中,我们使用 Visual Studio 自动生成的代理类来消费 WCF 服务。但是,您也可以手动定义动态代理类来完成此操作,例如 ChannelFactory,它比原始的更灵活,但我不想深入研究 WCF。

  14. 按 F5 运行。


     

在前两个示例中,我演示了进度条的基本用法,我认为您现在对如何在解决方案中使用进度条有了很多想法。但请将这些想法记在心里,并跟随我学习下一个示例,关于如何使用 JavaScript 触发进度条。我认为当您从 JavaScript 进行调用时,这会非常有用。

JavaScript 触发的进度条与 WCF 调用协同工作 

在接下来的示例中,我将使用流行的 js 库 jQuery 来进行演示,而不是编写太多脚本。

  1. 在此处 下载 jQuery 库。在我的示例中,jQuery 的版本号是 1.9.1。使用最新版本也没有问题。然后,将您刚刚下载的文件放入您的解决方案中。我通常会创建一个 Js 文件夹来存储 js,并在其中创建一个名为“common”的子文件夹来存储实用程序,如 js 库和公共函数。
     
  2. 在页面中包含 js 文件。
    <script language="javascript" src="Js/common/jquery-1.9.1.min.js" type="text/javascript"></script> 
  3. 将进度条放置在测试页面中,如示例 1 和示例 2 所示。
    <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>   

    请注意高亮部分,该按钮不是服务器控件,我们为其添加了客户端的 onclick 事件。事件的详细信息将在以下内容中提及。

  4. 创建一个 JS 函数来处理事件。
    function CallWcfByJS(sender, progressBarID, labelID) {
        var guid = GenGUID();
        $.ajax({
            url: "https://: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);
    } 

    在之前的代码段中,我们使用预定义函数创建了一个唯一密钥。然后,我们使用 Ajax 调用一个 WCF 的 RESTful 服务来启动一个新的进程。$.ajax 是 jQuery 中的一个 AJAX 方法,默认执行异步调用,因此系统无需等待响应返回。然后,清除标签中的消息,禁用按钮,将唯一密钥设置为进度条并触发其运行。最后,定义一个进度条的客户端完成事件,该事件将在进度条完成后执行。

  5. 在代码隐藏文件中为进度条添加完成事件。
    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. 导航到 WCF 服务项目,并添加一个具有单向消息交换模式的 RESTful 操作。
    [ServiceContract]
    public interface IProgressBarJsSer
    {
        [OperationContract(IsOneWay = true)]
        [WebGet(UriTemplate = "StartProcessJS/{guidStr}")]
        void StartProcess_JS(string guidStr);
    } 
  7. 实现接口。
    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. 在 *web.config* 中配置 RESTful 服务。
    <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. 按“F5”运行。

具有可自定义信息的进度条与 WCF 调用协同工作 

在接下来的示例中,我将介绍如何基于模板自定义进程信息。

 

  1. 在测试页面中添加一个进度条,并设计一个模板来显示进程信息。

     

            ......
                <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>
            ...... 

     

    在之前的代码段中,我们设计了一个网格来显示进程信息。实际上,您可以先在另一个页面设计您的可自定义布局,然后将其放入名为“CustomizableTemplate”的标签中。以“@”为前缀的单词将被进程信息的 [数据] 替换。在这里,我们声明了 @SeqNo、@CreateTime、@Amount 和 @TotalPrice。下一步,我们创建一个类来存储这些数据在服务器端。

  2. 创建一个 DataContract 来存储将返回给进度条的可自定义信息。

     

    [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);
            }
        }
    } 

     

    在之前的代码段中,我们创建了一个名为“OrderInfo”的类,它有四个属性。所有这些属性的类型都是 String。因为很难在客户端自动将对象转换为特定格式,所以我必须在将数据发送回客户端之前在服务器端进行转换。

  3. 创建一个名为 ProcessStatus_Order 的类,它继承自 ProcessStatus,并将 OrderInfo 包装其中,以便使用该服务的任何人不仅可以检索进程的基本信息,还可以检索其中的可自定义信息。

     

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

     

  4. 为了演示如何启动带有可自定义信息的进程、获取进程信息以及在进程完成后从服务器内存中删除进程信息,我们在服务合同中添加了三个操作。
    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);
    } 

    请注意高亮的代码,我们使用 ProcessStatus_Order 而不是 ProcessStatus。

  5. 创建一个新的 http handler 来从 WCF 服务检索进程状态和可自定义信息,然后将进程状态和可自定义信息分配给进度条。在下面的代码段中,我们通过 JavaScriptSerializer 将可自定义信息(即 OrderInfo)序列化为 JSON 格式,并将其分配给 progressBarStatus 的 MagInJson 属性。
    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. 将处理程序注册到应用程序。有关详细信息,请参阅上面部分。
  7. 为按钮添加点击事件,并在页面加载时为进度条添加完成事件。
  8. 按“F5”运行。

 

具有可自定义信息的进度条由 JavaScript 触发并与 WCF 调用协同工作

以下示例与前面的示例类似。但它是由 JavaScript 触发的。

 

 

  1. 向进度条添加自定义模板,并为按钮分配客户端事件。
    ......
    <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');" />   

    在这里,我添加了三个变量:@EmpNo、@LastHireDate 和 @Salary。然后为按钮附加一个点击事件。

  2. 补充通过 RESTful 服务启动进程的 JS 函数的详细信息。
    function CallWcfByJSWithCT(sender, progressBarID, labelID) {
        var guid = GenGUID();
        $.ajax({
            url: "https://: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. 创建一个 DataContract 来存储可自定义信息。
    [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. 创建一个类来包装继承自 ProcessStatus 的 Employee Info。
    [DataContract]
    [KnownType(typeof(EmployeeInfo))]
    public class ProcessStatus_Employee : ProcessStatus
    {
        [DataMember]
        public EmployeeInfo employeeInfo
        {
            get;
            set;
        }
    } 
  5. 创建一个 http handler 来检索可自定义信息。(前面已提及。)
  6. 将处理程序注册到应用程序。
  7. 在页面加载时为进度条添加完成事件。
        .......
        #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. 按“F5”运行。

至此,我已经介绍了进度条的五个基本用法,您可以下载附件中的示例项目。我希望这个控件能帮助您改善用户体验,并希望您能根据自己的需求发现其他用法。此外,我将继续改进这个控件,其中一些功能如上所述是我想要添加到控件中的,所以我希望您能关注我并收藏这篇文章以便以后获得更多信息。感谢您的阅读,如果您有任何问题,请随时与我联系。谢谢!

历史   

  • 2013-09-07 发布第一个版本。
  • 2013-09-18 修复缓存问题。将随机数作为参数附加到相对 URL(即)以防止其被缓存。感谢 Marco!
  • 2013-10-06 自定义模板以显示进程信息。
  • 2016-01-27 修复显示问题。兼容最新的浏览器,包括 IE 11、Chrome、Edge。
© . All rights reserved.