在异步回发期间发出并发AJAX WCF Web服务请求

时间:2010-05-22 06:28:42

标签: asp.net ajax webforms wcf scriptmanager

我希望在使用AJAX的ASP.NET WebForms页面上的长时间运行任务期间提供状态更新。

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

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

我有一个带有按钮的UpdatePanel,用于触发异步回发,该回执执行长时间运行的任务。我还有一个AJAX WCF Web服务的实例,该服务正常工作以获取数据并将其呈现在页面上,但是,正如我所说,它在异步回发完成之前不会获取和呈现数据。

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

问题是我可以调试并逐步完成Web服务并查看状态更新是否已正确设置,但是在异步回发完成之前,客户端脚本不会检索更新。

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

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

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

4 个答案:

答案 0 :(得分:0)

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

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

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

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

答案 1 :(得分:0)

在使用Web Developent Helper进行进一步检查后,似乎Web服务请求是按照我设置的间隔(5秒)进行的,但是在长时间运行的任务期间的第一个请求将任务的持续时间设置为返回结果,而后续请求继续返回任何内容。任务完成后,任务开始时发送的第一个Web服务请求将返回状态数据。

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

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

所以:

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

答案 2 :(得分:0)

为将来可能会发现此问题的任何人提供解决方案的后续行动。

我尝试了多种方式发送请求:

  • 将WCF服务作为ServiceReference添加到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。

网络服务:

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

答案 3 :(得分:0)

在你的剧本管理员之后把它扔进去。

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