У меня это работает, но я хочу поделиться этим, чтобы увидеть, пропустил ли я что-нибудь очевидное, и разгадать загадку, почему размер моего куска файла должен быть кратным 2049. Основные требования:
- Файлы, загруженные с веб-сайта, должны храниться на сервере SQL, а не как файлы
- Веб-сайт должен иметь возможность загружать и отображать данные файла в виде файла (открывается в отдельном окне.
- Веб-сайт - angularjs / javascript SPA, без серверного кода, без MVC
- API - это Web API 2 (опять же не MVC)
Я просто собираюсь сосредоточиться на загрузкечасть здесь. В основном то, что я делаю, это:
- Чтение фрагмента данных из поля varbinary сервера SQL
- API API Web 2 возвращает имя файла, тип mime и байтовые данные какстрока base64. ПРИМЕЧАНИЕ - попытался вернуть байтовый массив, но Web API все равно просто сериализует его в строку base64.
- объединяет фрагменты, преобразует фрагменты в большой двоичный объект и отображает библиотеку
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.Если у кого-то есть советы по этому вопросу, дайте мне знать.