0

I'm having an issue with an ASP.Net webforms application that uses ajax calls (via jQuery) to load secondary files into an iframe. Those secondary files zip up a bunch of files and then stream them to the user. I have a modal that shows a loading animation while this is happening, but when I set it to be removed when the ajax is complete, it still sometimes takes several seconds or even much longer for the modal to dissapear.

Here's the button:

<asp:Button ID="btnDownloadAll" runat="server" 
    Text="Download All Files" 
    Enabled="false" visibe="false"
    OnClientClick="javascript:document.getElementById('modal').style.width='100%'; downloadAll();"  
    OnClick="btnGenericDownload_Click" 
    ClientIDMode="Static" 
    CssClass="buttons" />

btnGenericDownload_Click in the codebehind doesn't actually do anything. Here's the JS function being called:

function downloadAll() {
            $.ajax({
                url: 'GenerateDownloadFile.aspx?filetype=all',
                type: 'GET',
                done: function () {
                    var iframe = document.createElement("iframe");
                    iframe.src = "GenerateDownloadFile.aspx?filetype=all";
                    iframe.style.display = "none";
                    document.body.appendChild(iframe);
                },
                complete: function () {
                    clearScreen();
                }
            });
        }

function clearScreen() {
            document.getElementById("modal").style.width = "1px";
        }

I'm wondering if there's any way to get this to sync better - to detect when the save as is being shown or when the file exists in the folder, which I am able to do in the codebehind, but not sure how to integrate that into the dynamic aspect of the ajax request. Currently the function that checks for the file is activated when certain form elements are clicked or changed...

Edit: just tried changing the downloadAll() function to this:

function downloadAll() {
        document.getElementById('modal').style.width = '100%';
        $.ajax({
            url: 'GenerateCNDownloadFile.aspx?filetype=all',
            type: 'GET',
            success: function () {
                var iframe = document.createElement("iframe");
                iframe.src = "GenerateCNDownloadFile.aspx?filetype=all";
                iframe.style.display = "none";
                document.body.appendChild(iframe);

            },
            done: function () {
                clearScreen();
            }
        });
    }

The file that is being called GenerateCNDownloadFile.aspx, has the following function in its codebehind which is what actually streams the file to the browser and initiates the Save As dialog:

public void StreamFileToBrowser(string sfilename, byte[] fileBytes)
    {
        try
        {
            Response.Clear();
            Response.ClearHeaders();

            Response.AppendHeader("Content-disposition", String.Format("attachment; filename=\"{0}\"", System.IO.Path.GetFileName(sfilename)));
            Response.AppendHeader("Content-Type", "binary/octet-stream");
            Response.AppendHeader("Content-length", fileBytes.Length.ToString());

            Response.BinaryWrite(fileBytes);

            if (Response.IsClientConnected)
                Response.Flush();
        }
        catch (Exception ex)
        {
            ErrorLog("StreamFileToBrowser: " + ex.Message);
        }
    }
Levi Wallach
  • 369
  • 5
  • 17
  • Out of curiosity, how does this work with the button `Enabled="false" visibe="false"`? (and visible is misspelled. could that be the problem?) – wazz Aug 28 '21 at 23:14
  • Have you tried putting `clearScreen()` in the `done` section? Also see what happens when you put the js from `OnClientClick` into the `downloadAll` function, before the ajax call. – wazz Aug 29 '21 at 00:35
  • Those are the initial values of the button, but they become true after another select list chooses a valid option. – Levi Wallach Aug 30 '21 at 20:44
  • @wazz, just added a done: section to the ajax statement and moved clearScreen into it, but now what happens is that the animation never goes away, it seems like it never gets run. I set breakpoints in Chrome Dev tools and the success items get run, but it never does get to the done line... – Levi Wallach Aug 30 '21 at 20:55
  • I was just spitballing, not sure what would work. Hard to tell with the iframe. Better go back. But one last idea, if you add a ScriptManager to the page you might have some luck using the page request manager. See [here](https://learn.microsoft.com/en-us/previous-versions/aspnet/bb386571(v=vs.100)). It's very handy, but still not sure about the iframe scenario. – wazz Aug 31 '21 at 00:45
  • Shouldn't it be "complete" instead of "done"? So "success" if the request was successfull and "done" when its complete. As far as I know there is no "complete" for jQuery ajax. Maybe this helps. – Jonas Weinhardt Aug 31 '21 at 13:24
  • @JonasWeinhardt, I tried moving the clearScreen() into complete and got the same result. Apparently it's triggered immediately after the success: is triggered, but then I'm still waiting another 10 seconds or more for the save as prompt to show up... – Levi Wallach Aug 31 '21 at 15:16
  • Can you maybe show us the `GenerateCNDownloadFile.aspx?filetype=all` function from your Code Behind? Would be very helpfull to understand the problem more :D – Jonas Weinhardt Aug 31 '21 at 17:53
  • @JonasWeinhardt I just added the function that is pushing the file to the browser to the end of my post. – Levi Wallach Aug 31 '21 at 18:42
  • not sure you can actually do what you are trying to do, but you can simplify things by just adding the iframe. (to send 1 GET instead of 2....) OR just creating the iframe ahead of time and target the iframe in the link/form. I think there are techniques that use the browser's localstorage, but size would be limited. – pcalkins Aug 31 '21 at 19:05
  • Maybe check here: http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/ I think they poll the iframe for contents and cookies. – pcalkins Aug 31 '21 at 19:26
  • Do you really need the "Save as" Dialog? Cause I think (and im not 100% sure about that) that you cant achieve that with your current method. If you dont need it you could take a look at [this](https://stackoverflow.com/a/9834261/16500604) – Jonas Weinhardt Sep 01 '21 at 05:48
  • @pcalkins what do you mean exactly by "by just adding the iframe"? I see that I'm referring to the url twice, and I can pull everything but the clearScreen() out of the success section, but it still has the same issue. – Levi Wallach Sep 03 '21 at 17:10
  • just that you're making two GETs. (probably not a big deal cause the 2nd one will abort the first...depending on when it gets "success" back) In your Ajax call you're sending a GET, and when you create the Iframe, it's loading that URL which sends a GET. It's not a part of your problem here... but you can simplify things by making 1 GET. To create some sort of progress I think you need to poll the iframe for contents, use cookies and poll for those, and/or check readystate of the iframe. (not real sure what readystate does when getting a file...) – pcalkins Sep 03 '21 at 17:22
  • 1
    basically the AJAX call isn't necessary... create the iframe and it will send the get and retrieve the file. Set a poll on that frame... (setInterval, or setTimeout,..) – pcalkins Sep 03 '21 at 17:28

1 Answers1

0

Well, after trying a lot of different ways to create the iframe and checking whether it was loaded and not having much I finally came across this answer (https://stackoverflow.com/a/23797348/773648) to a similar question and was able to use the code and it appears to be working. Here is the new downloadAll() function:

function downloadAll() {
        document.getElementById('modal').style.width = '100%';
        $.ajax({
            url: 'GenerateCNDownloadFile.aspx?filetype=all',
            type: 'GET',
            xhrFields: {
                responseType: 'blob' // to avoid binary data being mangled on charset conversion
            },
            success: function (blob, status, xhr) {
                // check for a filename
                var filename = "";
                var disposition = xhr.getResponseHeader('Content-Disposition');
                if (disposition && disposition.indexOf('attachment') !== -1) {
                    var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
                    var matches = filenameRegex.exec(disposition);
                    if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
                    clearScreen();
                }

                if (typeof window.navigator.msSaveBlob !== 'undefined') {
                    // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
                    window.navigator.msSaveBlob(blob, filename);
                } else {
                    var URL = window.URL || window.webkitURL;
                    var downloadUrl = URL.createObjectURL(blob);

                    if (filename) {
                        // use HTML5 a[download] attribute to specify filename
                        var a = document.createElement("a");
                        // safari doesn't support this yet
                        if (typeof a.download === 'undefined') {
                            window.location.href = downloadUrl;
                        } else {
                            a.href = downloadUrl;
                            a.download = filename;
                            document.body.appendChild(a);
                            a.click();
                        }
                    } else {
                        window.location.href = downloadUrl;
                    }

                    setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
                }
            }
        });
    }
Levi Wallach
  • 369
  • 5
  • 17