4
votes

I have been sorting through this issue all day and hope someone can help pinpoint my problem. I have created a "asynchronous progress callback" type functionality in my app using ajax. When I strip the functionality out into a test application I get the desired results. See image below:

Desired Functionality enter image description here

When I tie the functionality into my single page application using the same code I get a sort of blocking issue where all requests are responded to only after the last task has completed. In the test app above all request are responded to in order. The server reports a ("pending") state for all requests until the controller method has completed. Can anyone give me a hint as to what could cause the change in behavior?

Not Desired enter image description here

Desired Fiddler Request/Response

GET http://localhost:12028/task/status?_=1383333945335 HTTP/1.1
X-ProgressBar-TaskId: 892183768
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://localhost:12028/
Accept-Language: en-US
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)
Connection: Keep-Alive
DNT: 1
Host: localhost:12028

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 3.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcUHJvamVjdHNcVEVNUFxQcm9ncmVzc0Jhclx0YXNrXHN0YXR1cw==?=
X-Powered-By: ASP.NET
Date: Fri, 01 Nov 2013 21:39:08 GMT
Content-Length: 25

Iteration completed...

Not Desired Fiddler Request/Response

GET http://localhost:60171/_Test/status?_=1383341766884 HTTP/1.1
X-ProgressBar-TaskId: 838217998
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://localhost:60171/Report/Index
Accept-Language: en-US
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)
Connection: Keep-Alive
DNT: 1
Host: localhost:60171
Pragma: no-cache
Cookie: ASP.NET_SessionId=rjli2jb0wyjrgxjqjsicdhdi; AspxAutoDetectCookieSupport=1; TTREPORTS_1_0=CC2A501EF499F9F...; __RequestVerificationToken=6klOoK6lSXR51zCVaDNhuaF6Blual0l8_JH1QTW9W6L-3LroNbyi6WvN6qiqv-PjqpCy7oEmNnAd9s0UONASmBQhUu8aechFYq7EXKzu7WSybObivq46djrE1lvkm6hNXgeLNLYmV0ORmGJeLWDyvA2


HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?QzpcUHJvamVjdHNcSUxlYXJuLlJlcG9ydHMuV2ViXHRydW5rXElMZWFybi5SZXBvcnRzLldlYlxfVGVzdFxzdGF0dXM=?=
X-Powered-By: ASP.NET
Date: Fri, 01 Nov 2013 21:37:48 GMT
Content-Length: 25

Iteration completed...

The only difference in the two requests headers besides the auth tokens is "Pragma: no-cache" in the request and the asp.net version in the response.

Thanks

Update - Code posted (I probably need to indicate this code originated from an article by Dino Esposito )

var ilProgressWorker = function () {
    var that = {};
    that._xhr = null;
    that._taskId = 0;
    that._timerId = 0;
    that._progressUrl = "";
    that._abortUrl = "";
    that._interval = 500;
    that._userDefinedProgressCallback = null;
    that._taskCompletedCallback = null;
    that._taskAbortedCallback = null;
    that.createTaskId = function () {
        var _minNumber = 100,
            _maxNumber = 1000000000;
        return _minNumber + Math.floor(Math.random() * _maxNumber);
    };

    // Set progress callback
    that.callback = function (userCallback, completedCallback, abortedCallback) {
        that._userDefinedProgressCallback = userCallback;
        that._taskCompletedCallback = completedCallback;
        that._taskAbortedCallback = abortedCallback;
        return this;
    };

    // Set frequency of refresh
    that.setInterval = function (interval) {
        that._interval = interval;
        return this;
    };

    // Abort the operation
    that.abort = function () {
        //        if (_xhr !== null)
        //            _xhr.abort();
        if (that._abortUrl != null && that._abortUrl != "") {
            $.ajax({
                url: that._abortUrl,
                cache: false,
                headers: { 'X-ProgressBar-TaskId': that._taskId }
            });
        }
    };

    // INTERNAL FUNCTION
    that._internalProgressCallback = function () {
        that._timerId = window.setTimeout(that._internalProgressCallback, that._interval);
        $.ajax({
            url: that._progressUrl,
            cache: false,
            headers: { 'X-ProgressBar-TaskId': that._taskId },
            success: function (status) {
                if (that._userDefinedProgressCallback != null)
                    that._userDefinedProgressCallback(status);
            },
            complete: function (data) {
                var i=0;
            },
        });
    };

    // Invoke the URL and monitor its progress
    that.start = function (url, progressUrl, abortUrl) {
        that._taskId = that.createTaskId();
        that._progressUrl = progressUrl;
        that._abortUrl = abortUrl;

        // Place the Ajax call
        _xhr = $.ajax({
            url: url,
            cache: false,
            headers: { 'X-ProgressBar-TaskId': that._taskId },
            complete: function () {
                if (_xhr.status != 0) return;
                if (that._taskAbortedCallback != null)
                    that._taskAbortedCallback();
                that.end();
            },
            success: function (data) {
                if (that._taskCompletedCallback != null)
                    that._taskCompletedCallback(data);
                that.end();
            }
        });

        // Start the progress callback (if any)
        if (that._userDefinedProgressCallback == null || that._progressUrl === "")
            return this;
        that._timerId = window.setTimeout(that._internalProgressCallback, that._interval);
    };

    // Finalize the task
    that.end = function () {
        that._taskId = 0;
        window.clearTimeout(that._timerId);
    }

    return that;
};

Update 1 - Many thanks to John Saunders. I was able to locate this article that explains what John was implying in his comment about serialized access

"It blocks parallel execution and forces parallel requests to be executed one after another because the access to ASP.NET Session state is exclusive per session"

1
Can you post the code?Brad M
I bet that the code being called by your AJAX uses session state. ASP.NET will serialize accesses to session state.John Saunders
Many thanks John! When I added forms authentication and session state to the test client application I was able to notice the same behavior as in my application.Ross Bush
I wonder if there is a property on session that can change the "serialized access"?Ross Bush

1 Answers

1
votes

I found a fix at last. The session state can be controlled at the Controller and/or Controller Method level. Since the authorization is being verified at a higher level there is no need to use session in what I am doing. I simply disable it for the unit of work.

 [SessionState(SessionStateBehavior.Disabled)]
 public class _TestController : ProgressWorkerController