2010-05-22 21 views
0

我想在带有AJAX的ASP.NET WebForms页面上的长时间运行任务期间提供状态更新。在异步回传期间发出并发AJAX WCF Web服务请求

有没有办法让ScriptManager执行和处理异步回发并发的Web服务请求脚本?

我在页面上有一个脚本发出Web服务请求。它在页面加载时运行,并定期使用setInterval()。它在异步回发启动之前正确运行,但在异步回发期间它将停止运行,并且在异步回发完成之前不会再次运行。

我有一个UpdatePanel和一个按钮来触发一个异步回传,它执行长时间运行的任务。我还有一个AJAX WCF Web服务的实例,它正在正确地工作以获取数据并将其呈现在页面上,但正如我所说的,直到异步回发完成之后才会获取并呈现数据。

在异步回发期间,长时间运行的任务将更新从页面发送到Web服务。

问题是我可以调试并逐步浏览Web服务,并看到状态更新已正确设置,但客户端脚本未检索到更新,直到异步回发完成。

似乎脚本管理器正忙于执行异步回发,所以它不会通过setInterval()运行我的其他JavaScript,直到回发完成。

有没有办法让异步回发期间脚本管理器或其他方式运行脚本以从WCF Web服务中获取数据?

我已经尝试过使用PageRequestManager在异步回发的客户端BeginRequest事件上运行脚本的各种方法,但它运行脚本,然后停止处理应通过setInterval()运行的代码该页面请求执行。

回答

0

后续解决方案适用于任何未来可能会遇到的问题。

我试图发送请求的方式多种多样:当您添加WCF服务的服务引用到的ScriptManager

  • 的AJAX脚本对象创建。因此,如果WCF服务类是带有GetProgress方法的ProgressService,我在JavaScript中创建了一个新的ProgressService对象,并称为progressService.GetProgress()。
  • XmlHttpRequest请求。
  • jQuery $ .getJson()请求。
  • 一个Sys.Net.WebRequest请求。
  • 一个Sys.Net.WebServiceProxy请求。

事实证明,即使客户端请求被发送,即没有被ASP.NET ScriptManager缓存,如果它是IIS中同一网站的一部分,WCF服务也不会响应。

因此,我转而使用传统的ASP.NET(SOAP)Web服务(.asmx),而不是创建完全独立的WCF项目和完全独立的IIS网站。

我能够保持.asmx服务在Visual Studio中的同一个项目的一部分,并在IIS中的网站。请求在回发期间发送,服务在回发期间响应。

在ScriptManager中将它作为ServiceReference添加后,我也能够使用基本相同的脚本对象,创建一个新的ProgressWebService(),然后调用progressWebService.GetProgress()。传递给GetProgress()的回调处理程序然后处理响应并更新UI。

Web服务:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Web; 
using System.Web.Services; 
using System.Web.Caching; 

namespace MyNamespace 
{ 
    public class Progress 
    { 
     public string Message { get; set; } 
     public bool Complete { get; set; } 
    } 

    [WebService(Namespace = "MyNamespace")] 
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] 
    [System.ComponentModel.ToolboxItem(false)] 
    // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
    [System.Web.Script.Services.ScriptService] 
    public class ProgressWebService : System.Web.Services.WebService 
    { 
     protected static Dictionary<string, Progress> ProgressMessages = new Dictionary<string, Progress>(); 

     [WebMethod] 
     public Progress GetProgress(string progressId) 
     { 
      return ProgressMessages.ContainsKey(progressId) ? ProgressMessages[progressId] : new Progress(); 
     } 

     [WebMethod] 
     public void SetProgress(string progressId, string progress, bool complete) 
     { 
      if (ProgressMessages.ContainsKey(progressId)) 
      { 
       ProgressMessages[progressId].Message = progress; 
       ProgressMessages[progressId].Complete = complete; 
      } 
      else 
       ProgressMessages.Add(progressId, new Progress() { Message = progress, Complete = complete }); 
     } 

     [WebMethod] 
     public void SetProgressComplete(string progressId, bool complete) 
     { 
      if (ProgressMessages.ContainsKey(progressId)) 
       ProgressMessages[progressId].Complete = complete; 
      else 
       ProgressMessages.Add(progressId, new Progress() { Complete = complete }); 
     } 

     [WebMethod] 
     public void AddProgress(string progressId, string progress) 
     { 
      if (ProgressMessages.ContainsKey(progressId)) 
       ProgressMessages[progressId].Message += progress; 
      else 
       ProgressMessages.Add(progressId, new Progress() { Message = progress }); 
     } 
    } 
} 

客户端:

<%@ Page language="c#" CodeFile="About.aspx.cs" Inherits="MyNamespace.About" %> 
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %> 

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

<html xmlns="http://www.w3.org/1999/xhtml"> 
<head runat="server"> 
    <script type="text/javascript"> 
     var ProgressServiceInterval; // global interval var, so it can be set and cleared across functions 

     function btnBackup_Click(sender, e) { 
      sender.disabled = true; // disable the backup button, so the request isn't duplicated 
      // start getting the backup progress from the web service 
      var progressService = new MyNamespace.ProgressWebService(); 
      progressService.SetProgressComplete('<%= strBackupProgressGuid %>', false, null, null, null); 
      ProgressServiceInterval = setInterval('setBackupProgress()', 1000); // get progress once per second 
     } 

     function setBackupProgress() { 
      var progressService = new MyNamespace.ProgressWebService(); 
      progressService.GetProgress('<%= strBackupProgressGuid %>', progressCallback, null, null); 
     } 

     function progressCallback(result) { 
      var txtBackupOutput = $get('<%= txtBackupOutput.ClientID %>'); 
      try { 
       // show the progress message 
       txtBackupOutput.value = result.Message; 
       // stop checking if progress is complete 
       if (result.Complete == true) clearInterval(ProgressServiceInterval); 
       // scroll the textarea to the bottom 
       txtBackupOutput.scrollTop = txtBackupOutput.scrollHeight - txtBackupOutput.clientHeight; 
      } catch (ex) { 

      } 
     } 
    </script>   
</head> 
<body> 
    <form id="frmMyForm" method="post" runat="server"> 
     <ajaxToolkit:ToolkitScriptManager runat="Server" EnablePartialRendering="true" EnablePageMethods="true" ID="ScriptManager1" > 
      <Services> 
       <asp:ServiceReference Path="ProgressWebService.asmx" /> 
      </Services> 
     </ajaxToolkit:ToolkitScriptManager> 

     <asp:UpdatePanel ID="updBackup" runat="server" RenderMode="Inline"> 
      <ContentTemplate> 
       <asp:UpdateProgress ID="updBackupProgress" AssociatedUpdatePanelID="updBackup" runat="server" DynamicLayout="false"> 
        <ProgressTemplate> 
         <div style="text-align:center;margin-bottom:-32px;"> 
          <img src="loading.gif" alt="Loading..." /> 
         </div> 
        </ProgressTemplate> 
       </asp:UpdateProgress> 

       <asp:Button ID="btnBackup" runat="server" CssClass="SubmitButton" Text="Back Up Data" UseSubmitBehavior="false" OnClientClick="btnBackup_Click(this, event);" /> 
       <br /><br /> 

       <asp:TextBox ID="txtBackupOutput" runat="server" ReadOnly="true" TextMode="MultiLine" Rows="10" Width="100%" Wrap="true" /> 

      </ContentTemplate> 
     </asp:UpdatePanel> 
    </form> 
</body> 
</html> 

和服务器端:

namespace MyNamespace 
{ 
    public partial class About 
    { 
     protected string strBackupProgressGuid 
     { 
      get 
      { 
       if (Session["strBackupProgressGuid"] == null) 
        Session["strBackupProgressGuid"] = Guid.NewGuid().ToString(); 
       return Session["strBackupProgressGuid"] as string; 
      } 
     } 

     ProgressWebService _progressService; 
     protected ProgressWebService progressService 
     { 
      get 
      { 
       return _progressService = _progressService ?? new ProgressWebService(); 
      } 
     } 

     void btnBackup_Click(object sender, EventArgs e) 
     { 
      progressService.SetProgress(strBackupProgressGuid, "Started\r\n", false); 
      System.Threading.Thread.Sleep(10000); 
      progressService.AddProgress(strBackupProgressGuid, "+10\r\n"); 
      System.Threading.Thread.Sleep(10000); 
      progressService.AddProgress(strBackupProgressGuid, "+20\r\n"); 
      System.Threading.Thread.Sleep(10000); 
      progressService.AddProgress(strBackupProgressGuid, "+30\r\n"); 

      progressService.SetProgressComplete(strBackupProgressGuid, true); 
     } 
    } 
} 
0

ajax管道可能会排队您的请求。

尝试使用XHR或jQuery手动进行状态调用。你可能会发现解决了这个问题。

但请记住,一次可以发生的并发请求数量有限,并且一旦达到限制,就会阻塞开始发生。

此限制因浏览器/版本而异。

0

经过与Web Developent Helper的进一步检查,似乎Web服务请求正在以设置的间隔(5秒)进行,但长时间运行任务期间的第一个请求将使用任务的持续时间返回结果,而随后的请求继续没有返回。任务完成后,任务开始时发送的第一个Web服务请求将返回状态数据。

我一直在试图找出为什么初始请求到Web服务不会返回,直到任务完成。在AsyncPostBack请求结束之前会话变量可能不会更新,所以我尝试了ASP.NET缓存,但这也不起作用。我尝试了一个局部变量与Web服务,在InstanceContextMode.PerSession和InstanceContextMode.Single模式。

我一直在关注MSDN Mag: July 2007: Cutting Edge的示例,但使用ASP.NET缓存似乎没有帮助AsyncPostBack。我将尝试直接在代码中调用WebMethod方法而不是AsyncPostBack,但文章说它应该工作,所以我想弄清楚为什么我的实现没有。

所以:

  1. 页面负载。
  2. setInterval('getUpdate()',5000)开始。
  3. getUpdate()调用Web服务并每5秒返回一个数据。
  4. 用户单击该按钮可在UpdatePanel中启动AsyncPostBack。
  5. 服务器端处理在长时间运行的任务上启动。
  6. getUpdate()调用Web服务。该请求正在等待处理。
  7. 长时间运行的任务继续进行。
  8. getUpdate()每5秒继续调用Web服务。每个请求都会返回空数据。
  9. 长时间运行的任务完成。
  10. AsyncPostBack完成并向浏览器返回响应。
  11. 优秀的Web服务请求被发送回应。
  12. getUpdate()返回Web服务响应并更新页面以显示结果。
0

抛出这个在你的ScriptManager后。

<script type="text/javascript"> 
     var prm = Sys.WebForms.PageRequestManager.getInstance(); 
     prm.add_initializeRequest(InitializeRequestHandler); 
     prm.add_endRequest(EndRequestHandler); 

     var pbQueue = new Array(); 
     var argsQueue = new Array(); 

     function InitializeRequestHandler(sender, args) { 
      if (prm.get_isInAsyncPostBack()) { 
       args.set_cancel(true); 
       pbQueue.push(args.get_postBackElement().id); 
       argsQueue.push(document.forms[0].__EVENTARGUMENT.value); 
      } 
     } 

     function EndRequestHandler(sender, args) { 
      if (pbQueue.length > 0) { 
       __doPostBack(pbQueue.shift(), argsQueue.shift()); 
      } 
     } 
    </script> 
+1

克里斯 - 你的代码中手动排队时回传被发起的请求,然后在当前回发完成发送请求。这不符合这里所述的目标,因为1)要点是在回发期间不断发送进度信息请求,而不是一旦回传完成,以及2)它针对客户端,这不是问题;问题原来是服务器端的WCF服务。另外,当为WCF或ASMX服务添加服务引用时,比使用手动回发来使用由.Net创建的代理对象更容易。 – nekno 2011-07-16 08:04:36