ASP. NET Индикатор занятости ядра при загрузке файла - PullRequest
2 голосов
/ 27 февраля 2020

На моей веб-странице, когда пользователь нажимает кнопку загрузки, из контроллера вызывается действие экспорта, создается файл Excel, а затем возвращается пользователю. В зависимости от параметров, переданных действию, иногда создание файла и его возврат пользователю может занять некоторое время.

Итак, я хочу показать индикатор занятости во время ожидания файла. Я пошел с Bootstrap spinner.

$('.content').on('click', 'a.export', function (e) {
    e.preventDefault();

    // show busy indicator
    $('#divSpinner').css('display', 'inline-block');

    var idSelect = '';
    $('#ddlSelect option:selected').each(function () {
        idSelect += $('#ddlSelect')[0].value;
    });

    var idSelect2 = '';
    $('#ddlSelect2 option:selected').each(function () {
        idSelect2 += $('#ddlSelect2')[0].value;
    });

    var url = 'Home/Exports?' + 'param1=' + idSelect + '&param2=' + idSelect2;

    //download file
    window.location = url;

    // hide busy indicator
    $('#divSpinner').css('display', 'none');
});

Ну, этот вид работ, но проблема в том, что $ ('# divSpinner'). css ('display', 'none' ); получает немедленный удар, и счетчик прячется до того, как файл фактически станет доступен пользователю для загрузки (загрузка / открытие в браузере).

Как мне переписать код для ожидания окна .location = url в fini sh, а затем скрыть счетчик?

РЕДАКТИРОВАТЬ: Я также пытался обернуть часть Download в асинхронную c функцию следующим образом:

async function Download(url) {

    let promise = new Promise((resolve, reject) => {
        resolve(window.location = url);
    });

        let result = await promise; // wait until the promise resolves (*)
}

и называет это так:

Download(url)
    .then(function() {
        $('#divSpinner').hide()
});

Загрузка работает, но все же $('#divSpinner').hide() get получает, прежде чем загрузка файла будет представлена ​​пользователю. Разве весь смысл асинхронного / ожидающего ожидания результата перед продолжением выполнения кода ??

EDIT2 (с «blob»):

async function Download(url) {
    let promise = new Promise((resolve, reject) => {
        resolve(filedownload(url));
    });
    let result = await promise; 
}

function filedownload(urlToSend) {
    var req = new XMLHttpRequest();
    req.open("GET", urlToSend, true);
    req.responseType = "blob";
    req.onload = function (event) {
        var blob = req.response;
        var fileName = req.getResponseHeader("fileName") 
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = fileName;
        link.click();
    };
    req.send();
}

и вызывая его так:

Download(url)
    .then(function() {
        $('#divSpinner').hide()
});

EDIT 3 : я использовал решение @ Nenad снизу, но я изменил только обработку заголовка ответа (в противном случае я получал NULL для имени файла при загрузке - я использовал это решение { ссылка }):

function filedownload(urlToSend, resolve) {
    var req = new XMLHttpRequest();
    req.open("GET", urlToSend, true);
    req.responseType = "blob";
    req.onload = function (event) {
        var blob = req.response;
        var filename = "";
        var disposition = req.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, '');
            }
        }
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = filename;
        link.click();
        resolve(filename); // call resolve after download has finished.
    };
    req.send();
}

Ответы [ 2 ]

1 голос
/ 14 марта 2020

TL; DR

Вы делегируете управление браузеру, поэтому на вашей странице завершенный процесс.

Подробно:

Метод, используемый для загрузки файла (window.location = url;), сообщает браузеру загрузить новую страницу вместо текущей один. Вы делегируете управление загрузкой новой страницы в браузер, как если бы url указывал бы на другую HTML страницу.

Только после того, как браузер обнаружил, что содержимое не HTML, а что-то другое основанный на заголовках, он загружает файл, вместо того, чтобы бросать текущую страницу и отображать новую.

Заголовки, которые направляют браузер для загрузки содержимого:

  • content-disposition: attachment
  • content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Другой подход будет использовать AJAX запрос на загрузку responseType=blob. Хороший пример - здесь . Недостатки этого метода:

  • Он не поддерживается в старых браузерах.
  • Полный файл загружается JavaScript памяти, прежде чем сохранить его.

Но у вас есть контроль над тем, что происходит после завершения загрузки blob.

ОБНОВЛЕНИЕ: Глядя на ваше второе редактирование, вы звоните resolve на раннюю версию. Кроме того, вы должны вернуть Promise из метода загрузки.

async function Download(url) {
    return new Promise((resolve, reject) => {
        filedownload(url, resolve); // pass resolve
    }); 
}

function filedownload(urlToSend, resolve) {
    var req = new XMLHttpRequest();
    req.open("GET", urlToSend, true);
    req.responseType = "blob";
    req.onload = function (event) {
        var blob = req.response;
        var fileName = req.getResponseHeader("fileName") 
        var link = document.createElement('a');
        link.href = window.URL.createObjectURL(blob);
        link.download = fileName;
        link.click();
        resolve(fileName); // call resolve after download has finished.
    };
    req.send();
}
0 голосов
/ 27 февраля 2020
var url = 'Home/Exports?' + 'param1=' + idSelect + '&param2=' + idSelect2;

$(document).load(url, function () {
    // hide busy indicator
    $('#divSpinner').css('display', 'none');
});

Я не уверен, но вы можете попробовать это?

...