开发者

Making a concurrent AJAX WCF Web Service request during an Async Postback

I want to provide status updates during a long-running task on an ASP.NET WebForms page with AJAX.

Is there a way to get the ScriptManager to execute and process a script for a web service request concurrently with an async postback?

I have a script on the page that makes a web service request. It runs on page load and periodically using setInterval(). It's running correctly before the async postback is initiated, but it stops running during the async postback, and doesn't run again until after the async postback completes.

I have an UpdatePanel with a button to trigger an async postback, which executes the long-running task. I also have an instance of an AJAX WCF Web service that is working correctly to fetch data and present it on the page but, like I said, it doesn't fetch and present the data until after the async postback completes.

During the async postback, the long-running task sends updates from the page to the web service.

The problem is that I can debug and step through the web service and see that the status updates are correctly set, but the updates aren't retrieved by the client script until the async postback completes.

It seems the Script Manager is busy executing the async postback, so it doesn't run my other JavaScript via setInterval() unt开发者_如何学Goil the postback completes.

Is there a way to get the Script Manager, or otherwise, to run the script to fetch data from the WCF web service during the async postback?

I've tried various methods of using the PageRequestManager to run the script on the client-side BeginRequest event for the async postback, but it runs the script, then stops processing the code that should be running via setInterval() while the page request executes.


The ajax plumbing may be queuing your requests.

Try making the status call manually with an XHR or jQuery. You may find that solves the problem.

But... keep in mind that there are a finite number of concurrent requests that can be happening at one time and that once the limit is reached blocking starts to happen.

This limit varies by browser/version.


Upon further inspection with Web Developent Helper, it appears that the web service requests are being made at the interval I set (5 secs), but the first request during the long-running task takes the duration of the task to return a result, while subsequent requests continue to return nothing. When the task completes, the first web service request sent when the task began returns with the status data.

I've been trying to figure out why the initial request to the web service doesn't return until the task completes. Session variables may not be updated until the end of the AsyncPostBack request, so I tried the ASP.NET cache, but that doesn't work either. I tried a local variable with the web service, both in InstanceContextMode.PerSession and InstanceContextMode.Single modes.

I've been following the example from MSDN Mag: July 2007: Cutting Edge, but using the ASP.NET cache doesn't seem to help with the AsyncPostBack. I'm going to try calling WebMethod methods in my code-behind directly instead of the AsyncPostBack, but the articles says it should work, so I'd like to figure out why my implementation doesn't.

So:

  1. Page loads.
  2. setInterval('getUpdate()', 5000) starts.
  3. getUpdate() calls a web service and returns emtpy data every 5 secs.
  4. User clicks the button to start an AsyncPostBack in an UpdatePanel.
  5. Server-side processing starts on the long-running task.
  6. getUpdate() calls the web service. The request is pending.
  7. The long-running task proceeds.
  8. getUpdate() continues to call the web service every 5 secs. Each request returns empty data.
  9. The long-running task completes.
  10. The AsyncPostBack completes and returns a response to the browser.
  11. The outstanding web service request is sent a response.
  12. getUpdate() returns the web service response and updates the page to display the result.


Throw this in after your 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>


A follow-up with a solution for anyone that may find this in the future.

I tried sending the request in a multitude of ways:

  • The AJAX script objects created when you add the WCF service as a ServiceReference to the ScriptManager. So, if the WCF service class is ProgressService with a GetProgress method, I created a new ProgressService object in JavaScript and called progressService.GetProgress().
  • An XmlHttpRequest request.
  • A jQuery $.getJson() request.
  • A Sys.Net.WebRequest request.
  • A Sys.Net.WebServiceProxy request.

It turns out that even if the client request is sent, i.e., not buffered by the ASP.NET ScriptManager, that the WCF service won't respond if it is part of the same website in IIS.

So rather than creating an entirely separate WCF project, and an entirely separate IIS website, I switched to a traditional ASP.NET (SOAP) web service (.asmx).

I was able to keep the .asmx service part of the same project in Visual Studio, and website in IIS. The request is sent during a postback, and the service responds during a postback.

After adding it as a ServiceReference under the ScriptManager, I was also able to use essentially the same scripting objects, creating a new ProgressWebService(), then calling progressWebService.GetProgress(). A callback handler passed to GetProgress() then handles the response and updates the UI.

Web service:

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

Client side:

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

And the server-side:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜