Извлечение данных файла в чанках с использованием Web API для отображения в браузере (WIP) - PullRequest
0 голосов
/ 27 ноября 2018

У меня это работает, но я хочу поделиться этим, чтобы увидеть, пропустил ли я что-нибудь очевидное, и разгадать загадку, почему размер моего куска файла должен быть кратным 2049. Основные требования:

  • Файлы, загруженные с веб-сайта, должны храниться на сервере SQL, а не как файлы
  • Веб-сайт должен иметь возможность загружать и отображать данные файла в виде файла (открывается в отдельном окне.
  • Веб-сайт - angularjs / javascript SPA, без серверного кода, без MVC
  • API - это Web API 2 (опять же не MVC)

Я просто собираюсь сосредоточиться на загрузкечасть здесь. В основном то, что я делаю, это:

  1. Чтение фрагмента данных из поля varbinary сервера SQL
  2. API API Web 2 возвращает имя файла, тип mime и байтовые данные какстрока base64. ПРИМЕЧАНИЕ - попытался вернуть байтовый массив, но Web API все равно просто сериализует его в строку base64.
  3. объединяет фрагменты, преобразует фрагменты в большой двоичный объект и отображает библиотеку

VBфункция, которая возвращаетнабор данных с чанком (я должен использовать эту библиотеку, которая обрабатывает соединение с базой данных, но не поддерживает запросы параметров)

Public Function GetWebApplicationAttachment(ByVal intId As Integer, ByVal intChunkNumber As Integer, ByVal intChunkSize As Integer) As DataSet

    ' the starting number is NOT 0 based
    Dim intStart As Integer = 1
    If intChunkNumber > 1 Then intStart = ((intChunkNumber - 1) * intChunkSize) + 1
    Dim strQuery As String = ""
    strQuery += "SELECT FileName, "
    strQuery += "SUBSTRING(ByteData," & intStart.ToString & "," & intChunkSize.ToString & ") AS ByteData "
    strQuery += "FROM FileAttachments WHERE Id = " + intId.ToString + " "
    Try
        Return Query(strQuery)
    Catch ex As Exception
        ...
    End Try
End Function

Бит бизнес-правил веб-API, который создает объект файла из набора данных

...
    result.FileName = ds.Tables[0].Rows[0]["FileName"].ToString();
    // NOTE: Web API converts a byte array to base 64 string so the result is the same either way
    // the result of this is that the returned data will be about 30% bigger than the chunk size requested
    result.StringData = Convert.ToBase64String((byte[])ds.Tables[0].Rows[0]["ByteData"]);
    //result.ByteData = (byte[])ds.Tables[0].Rows[0]["ByteData"];
    ... some code to get the mime type
    result.MIMEType = ...

Контроллер веб-API (упрощено - все средства защиты и обработки ошибок удалены)

public IHttpActionResult GetFileAttachment([FromUri] int id, int chunkSize, int chunkNumber) {
    brs = new Files(...);
    fileResult file = brs.GetFileAttachment(appID, chunkNumber, chunkSize);
    return Ok(file);
}

angularjs Служба, которая с высокой точностью получает чанки и собирает их вместе

    function getFileAttachment2(id, chunkSize, chunkNumber, def, fileData, mimeType) {
        var deferred = def || $q.defer();
        $http.get(webServicesPath + "api/files/get-file-attachment?id=" + id + "&chunkSize=" + chunkSize + "&chunkNumber=" + chunkNumber).then(
            function (response) {
                // when completed string data will be empty
                if (response.data.StringData === "") {
                    response.data.MIMEType = mimeType;
                    response.data.StringData = fileData;
                    deferred.resolve(response.data);
                } else {
                    if (chunkNumber === 1) {
                        // only the first chunk computes the mime type
                        mimeType = response.data.MIMEType;
                    }
                    fileData += response.data.StringData;
                    chunkNumber += 1;
                    getFileAttachment2(appID, detailID, orgID, GUID, type, chunkSize, chunkNumber, deferred, fileData, mimeType);
                }
            },
            function (response) {
                ... error stuff
            }
        );
        return deferred.promise;
    }

угловой контроллерметод, который делает вызовы.

    function viewFile(id) {
        sharedInfo.getWebPortalSetting("FileChunkSize").then(function (result) {
            // chunk size must be a multiple of 2049 ???
            var chunkSize = 0;
            if (result !== null) chunkSize = parseInt(result);
            fileHelper.getFileAttachment2(id, chunkSize, 1, null, "", "").then(function (result) {
                if (result.error === null) {
                    if (!fileHelper.viewAsFile(result.StringData, result.FileName, result.MIMEType)) {
                        ... error
                    }
                    result = {};
                } else {
                    ... error;
                }
            });
        });
    }

И, наконец, бит javascript, который отображает файл как загрузочный файл

    function viewAsFile(fileData, fileName, fileType) {
        try {
            fileData = window.atob(fileData);
            var ab = new ArrayBuffer(fileData.length);
            var ia = new Uint8Array(ab);    // ia provides window into array buffer
            for (var i = 0; i < fileData.length; i++) {
                ia[i] = fileData.charCodeAt(i);
            }
            var file = new Blob([ab], { type: fileType });
            fileData = "";
            if (window.navigator.msSaveOrOpenBlob) // IE10+
                window.navigator.msSaveOrOpenBlob(file, fileName);
            else { // Others
                var a = document.createElement("a"),
                    url = URL.createObjectURL(file);
                a.href = url;
                a.download = fileName;
                document.body.appendChild(a);
                a.click();
                setTimeout(function () {
                    document.body.removeChild(a);
                    window.URL.revokeObjectURL(url);
                }, 0);
            }
            return true;
        } catch (e) {
            ... error stuff
        }
    }

Я уже вижу, что более RESTful подход будет использовать заголовкиуказать диапазон фрагментов и отделить метаданные файла от фрагментов файла.Также я мог бы попытаться вернуть поток данных, а не строку в кодировке Base64.Если у кого-то есть советы по этому вопросу, дайте мне знать.

1 Ответ

0 голосов
/ 28 ноября 2018

Ну, это был совершенно неправильный путь.В случае, если это поможет, вот что я в итоге сделал.

  • Динамически создайте адрес href якорного тега для возврата файла (токен безопасности и параметры в строке запроса)
  • get byteмассив из базы данных
  • ответное сообщение о возврате вызова web api (см. код ниже)

Это гораздо быстрее и надежнее, но обеспечивает меньше контроля хода выполнения.

метод бизнес-правил использует ...

...
file.ByteData = (byte[])ds.Tables[0].Rows[0]["ByteData"];
...

контроллер веб-API

public HttpResponseMessage ViewFileAttachment([FromUri] int id, string token) {
    HttpResponseMessage response = new HttpResponseMessage();
    ... security stuff
    fileInfoClass file = ... code to get file info
    response.Content = new ByteArrayContent(file.ByteData);
    response.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") {
            FileName = file.FileName
        };
    response.Content.Headers.ContentType = new MediaTypeHeaderValue(file.MIMEType);
    return response;

Это можно даже улучшить с помощью потоковой передачи

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...