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






4.90/5 (31投票s)
用于 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 服务器控件。更具体地说,这个进度条是通过一些实际项目实现的,并获得了许多好评。
功能
- 更有效。使用 Ajax 调用检索进程信息。
- 自定义每次调用的间隔。
- 允许在同一页面上放置多个进度条并同步启动。
- 自定义显示消息。
- 进程完成后可以添加服务器事件。
- 高内聚。将 JavaScript 代码和 CSS 样式嵌入到控件中。
- 能够使用 JS 启动进度条。
- 通过 WCF、Web 服务或 RESTful 服务检索完成百分比。
- 允许用户自定义模板以显示进程信息。(2013-10-06)
目前不包含。
允许用户自定义模板以显示进程信息。- 暂停进度条,然后继续。
使用代码
首先,我必须确保任何想要使用我的控件的人都对“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 服务项目并在其中添加一些操作。
-
在 VS2010 的顶部菜单中导航到“文件 -> 添加 -> 新建项目…”。
-
选择“WCF 服务应用程序”作为项目模板并为项目命名。
-
删除项目模板创建的原始服务,并添加一个名为“ProgressBarSer.svc”的新 wcf 服务。
-
在服务中添加三个操作,如下所示。
StartProcess(Guid guid)
:使用唯一密钥启动一个长时间运行的进程。(单向消息交换模式)GetProcessPrecentage(Guid guid)
:使用唯一密钥获取进程状态。ProcessComplete(Guid guid)
:进程完成后,从服务器内存中删除进程状态。
-
为了存储服务的进程状态,我们创建了一个名为“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; } } }
-
为了管理所有进程的状态,我们创建了一个名为“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 来存储所有进程状态,以便我们可以通过唯一密钥获取进程状态。另一方面,我们创建了一个静态对象,通过锁定它来确保一段代码能够完整运行,而不会被其他线程中断。
-
实现接口中的三个抽象方法。
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); }
-
在 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>
-
返回到 asp.net 项目并添加上面提到的服务引用。
-
创建一个实现 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 }
-
将处理程序注册到应用程序。
<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>
-
与第一个示例相同,在 .aspx 页面中插入一个由 UpdatePanel 包裹的新进度条。
-
为按钮添加点击事件,并在页面加载时为进度条添加完成事件。
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。
-
按 F5 运行。
在前两个示例中,我演示了进度条的基本用法,我认为您现在对如何在解决方案中使用进度条有了很多想法。但请将这些想法记在心里,并跟随我学习下一个示例,关于如何使用 JavaScript 触发进度条。我认为当您从 JavaScript 进行调用时,这会非常有用。
JavaScript 触发的进度条与 WCF 调用协同工作
在接下来的示例中,我将使用流行的 js 库 jQuery 来进行演示,而不是编写太多脚本。
- 在此处 下载 jQuery 库。在我的示例中,jQuery 的版本号是 1.9.1。使用最新版本也没有问题。然后,将您刚刚下载的文件放入您的解决方案中。我通常会创建一个 Js 文件夹来存储 js,并在其中创建一个名为“common”的子文件夹来存储实用程序,如 js 库和公共函数。
- 在页面中包含 js 文件。
<script language="javascript" src="Js/common/jquery-1.9.1.min.js" type="text/javascript"></script>
- 将进度条放置在测试页面中,如示例 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 事件。事件的详细信息将在以下内容中提及。
- 创建一个 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 方法,默认执行异步调用,因此系统无需等待响应返回。然后,清除标签中的消息,禁用按钮,将唯一密钥设置为进度条并触发其运行。最后,定义一个进度条的客户端完成事件,该事件将在进度条完成后执行。
- 在代码隐藏文件中为进度条添加完成事件。
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
- 导航到 WCF 服务项目,并添加一个具有单向消息交换模式的 RESTful 操作。
[ServiceContract] public interface IProgressBarJsSer { [OperationContract(IsOneWay = true)] [WebGet(UriTemplate = "StartProcessJS/{guidStr}")] void StartProcess_JS(string guidStr); }
- 实现接口。
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); } } }
- 在 *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>
- 按“F5”运行。
具有可自定义信息的进度条与 WCF 调用协同工作
在接下来的示例中,我将介绍如何基于模板自定义进程信息。
- 在测试页面中添加一个进度条,并设计一个模板来显示进程信息。
...... <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。下一步,我们创建一个类来存储这些数据在服务器端。
- 创建一个 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。因为很难在客户端自动将对象转换为特定格式,所以我必须在将数据发送回客户端之前在服务器端进行转换。
- 创建一个名为 ProcessStatus_Order 的类,它继承自 ProcessStatus,并将 OrderInfo 包装其中,以便使用该服务的任何人不仅可以检索进程的基本信息,还可以检索其中的可自定义信息。
[DataContract] [KnownType(typeof(OrderInfo))] public class ProcessStatus_Order : ProcessStatus { [DataMember] public OrderInfo orderInfo { get; set; } }
- 为了演示如何启动带有可自定义信息的进程、获取进程信息以及在进程完成后从服务器内存中删除进程信息,我们在服务合同中添加了三个操作。
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。
- 创建一个新的 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 }
- 将处理程序注册到应用程序。有关详细信息,请参阅上面部分。
- 为按钮添加点击事件,并在页面加载时为进度条添加完成事件。
- 按“F5”运行。
具有可自定义信息的进度条由 JavaScript 触发并与 WCF 调用协同工作
以下示例与前面的示例类似。但它是由 JavaScript 触发的。
- 向进度条添加自定义模板,并为按钮分配客户端事件。
...... <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。然后为按钮附加一个点击事件。
- 补充通过 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); }
- 创建一个 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); } } }
- 创建一个类来包装继承自 ProcessStatus 的 Employee Info。
[DataContract] [KnownType(typeof(EmployeeInfo))] public class ProcessStatus_Employee : ProcessStatus { [DataMember] public EmployeeInfo employeeInfo { get; set; } }
- 创建一个 http handler 来检索可自定义信息。(前面已提及。)
- 将处理程序注册到应用程序。
- 在页面加载时为进度条添加完成事件。
....... #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
- 按“F5”运行。
至此,我已经介绍了进度条的五个基本用法,您可以下载附件中的示例项目。我希望这个控件能帮助您改善用户体验,并希望您能根据自己的需求发现其他用法。此外,我将继续改进这个控件,其中一些功能如上所述是我想要添加到控件中的,所以我希望您能关注我并收藏这篇文章以便以后获得更多信息。感谢您的阅读,如果您有任何问题,请随时与我联系。谢谢!
历史
- 2013-09-07 发布第一个版本。
- 2013-09-18 修复缓存问题。将随机数作为参数附加到相对 URL(即)以防止其被缓存。感谢 Marco!
- 2013-10-06 自定义模板以显示进程信息。
- 2016-01-27 修复显示问题。兼容最新的浏览器,包括 IE 11、Chrome、Edge。