上传后的双倍大小的文件

时间:2016-12-09 22:34:26

标签: javascript c# angularjs

我正在管理一个Web应用程序,它接受来自用户的文件,然后将其上传到服务器中的文件位置。为了满足大尺寸文件(上面100 MB),我决定使用 blob.slice 方法。我的问题是,文件上传后我尝试下载它,文件大小是原始大小的两倍,因此导致它被破坏。我将展示从客户端到服务器端的数据流,以显示上载方法的逐步操作。

该指令是输入type =“file”的HTML和blob切片的逻辑所在的位置。

客户端

//Directive
var template = [
    '<div class="file-input">',

        '<div>',
            '<input type="text" ng:model="fileinfo.meta.name"  disabled />',
        '<div class="filebrowse">',
            '<button type="button" class="browsemodal">Browse</button>',
               '<input type="file"></input>',
                '</div>',
        '</div>',
    '</div>'
].join('');

module.exports.init = function (app) {

    app.directive('fileInput', [
        function () {
            return {
                restrict: 'E',
                template: template,
                replace: true,
                scope: {
                    fileinfo : '=ngModel'
                },
                link: function (scope, element) {                    

                    element.bind('change', function (ev) {
                        var fileSize = ev.target.files[0].size;
                        var chunkSize = 64 * 1024;
                        var offset = 0;
                        var self = this;
                        var chunkReaderBlock = null;

                        var readEventHandler = function (evt) {
                            offset += evt.target.result.length;
                            scope.fileinfo.meta = ev.target.files[0];
                            scope.fileinfo.data = ev.target.files[0];
                            scope.fileinfo.sampleData.push(evt.target.result);

                            if (offset >= fileSize) {
                                return;
                            }

                            chunkReaderBlock(offset, chunkSize, ev.target.files[0]);
                        };

                        chunkReaderBlock = function (_offset, length, _file) {
                            var reader = new FileReader();
                            var blob = _file.slice(_offset, length + _offset);

                            reader.onload = readEventHandler;
                            reader.readAsText(blob);
                        };

                        chunkReaderBlock(offset, chunkSize, ev.target.files[0]);
                    });
                }
            }
        }
    ]);
};

scope.fileinfo在工厂中表示一个名为 documentInfoModel 的属性,如下面的代码段所示。

//Factory    
documentInfoModel: function () {
    var self = this;
    self.meta = null;
    self.data = null;
    self.sampleData = [];
    return self;

现在,只要我点击上传按钮,它就会在控制器中触发名为 saveData 的功能。此函数将通过documentService.upsertDocument方法从服务器端调用http.Post到API。 API名为 AddFile 。请参阅下面的详细信息。

//Controller
$scope.saveData = function () {
    documentService.upsertDocument($scope.fileInfoItem).then(function (data) {
        //File was uploaded successfully
    };
};

服务器端

    public HttpResponseMessage AddFile(HttpRequestMessage request, [FromBody] DocumentInfoModel file)
                {
                    using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(0, 30, 0)))
                    {
                        try
                        {
                            StringBuilder sb = new StringBuilder();
                            foreach (string text in file.sampleData)
                                sb.Append(text);

                            byte[] data = Encoding.Unicode.GetBytes(sb.ToString());
                            var fileLocation = "C:\Temp\";
                            var targetFileName = file.data;

                            if (!Directory.Exists(fileLocation))
                                Directory.CreateDirectory(fileLocation);

                            File.WriteAllBytes(targetFileName, data);
                        }

                        catch()
                        {}
    return request.CreateResponse(HttpStatusCode.OK);
}

任何人都可以帮我识别代码中的任何错误吗?如果它有帮助,我将在这里放下载API。非常感谢!

private HttpResponseMessage Download(string fileName)
        {
            var filePath = "C:\Temp\";

            var res = new HttpResponseMessage();

            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
            {
                res.Content = new StreamContent(File.OpenRead(filePath));
                res.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
                res.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
                {
                    FileName = fileName
                };
                res.StatusCode = HttpStatusCode.OK;
            }
            else
                res.StatusCode = HttpStatusCode.InternalServerError;

            return res;
        }

2 个答案:

答案 0 :(得分:1)

在我的同事寻求帮助后,我们找到了解决问题的方法。也许我只是不知道如何在上传大文件时正确实现FileReader的异步方法,所以我们决定使用不同的方法。我们做的第一件事就是删除指令中的模板,并将指令修改为如下所示:

//Directive
app.directive('fileInput', [
        function () {
            return {
                restrict: 'EA',
                replace: true,
                scope: {
                    fileinfo: '=ngModel'
                },
                link: function (scope, element) {                    
                    element.bind('change', function (ev) {
                        scope.$apply(function () {
                            var val = element[0].files[0];
                            scope.fileinfo.fileName = ev.target.files[0];
                            scope.fileinfo.file = val;
                        });
                    });
                }
            }
        }
    ]);

然后我们在HTML文件本身内创建了模板(见下文):

<input type="text" ng:model="fileInfoItem.fileName" disabled />
<div class="filebrowse">
    <button type="button" class="browsemodal">Browse</button>
    <input name="file" file-input="fileinfo" ng-model="fileInfoItem" type="file" />
</div>

接下来,在控制器中我们使用FormData存储文件,然后我们将其发送到API。

//Controller
$scope.saveDocument = function () {
    var fd = new FormData();
    fd.append('file', $scope.fileInfoItem.file);
    documentService.upsertDocument($scope.fileInfoItem, fd)
    .then(function (data) { 
        //Upload was successful.
    };
};

//Services
upsertDocument: function (fileInfoItem, data) {
    console.log(data);
    var payload = {
        FileName: fileInfoItem.fileName
    };
    return apiCall = $http.post(API_ENDPOINT.upsertDocument(fileInfoItem.docId), payload {})
        .then(function (ret) {
            return $http.post(API_ENDPOINT.upsertDocumentFile(ret.data), data, {
                withCredentials: false,
                headers: {
                'Content-Type': undefined
                },
                transformRequest: angular.identity
            });
        });
    },

我们创建两个API的原因是因为我们无法将post文件中的文件和对象有效负载都传递给单个API。这可能不是最好的解决方案,但它确实适用于我们的应用程序。

答案 1 :(得分:0)

当您在二进制文件上调用reader.readAsText(blob);时,您将面临无法获取相同数据的风险......特别是当它与二进制文件有关时

举个例子,我用utf-16格式创建了一个blob(带有&#34的文本文件;测试1 2 3&#34;)

提示结果缓冲区不一样......

&#13;
&#13;
var buffer = new Uint8Array([
  255, 254, 84, 0, 101, 0, 115, 0, 116, 0, 105, 0, 110,
  0, 103, 0, 32, 0, 49, 0, 32, 0, 50, 0, 32, 0, 51, 0
]) // "Testing 1 2 3" buffer in UTF-16

var blob = new Blob([buffer])
var fr = new FileReader
fr.onload = () => {
  console.log(fr.result)
  let buffer = strToUint8(fr.result)
  document.body.innerHTML += '<br>result: ' + Array.from(buffer)
}
fr.readAsText(blob)

function strToUint8(str) {
  let buf = new ArrayBuffer(str.length*2) // 2 bytes for each char
  let bufView = new Uint16Array(buf)
  
  for (let i = 0, strLen = str.length; i < strLen; i++)
    bufView[i] = str.charCodeAt(i)
  
  return new Uint8Array(buf)
}
&#13;
actual: 255,254,84,0,101,0,115,0,116,0,105,0,110,0,103,0,32,0,49,0,32,0,50,0,32,0,51,0
&#13;
&#13;
&#13;

有关详情,请参阅此处:HTML5 File API read as text and binary

丑陋的

丑陋的部分是你试图用FileReader读取每个文件的内容,而事实上你并不需要。它只需要更多的时间来阅读内容,它也会占用更多的CPU和内存

好的

你只需要将blob切成你想要的大小而不必读取任何数据,然后将每个块上传为二进制(而不是文本)

var blob = new Blob(['...........'])
var chunks = []

const BYTES_PER_CHUNK = 2; // 2 byte chunk sizes.
const SIZE = blob.size;

var start = 0;
var end = BYTES_PER_CHUNK;

while(start < SIZE) {
  chunks.push(blob.slice(start, end));

  start = end;
  end = start + BYTES_PER_CHUNK;
}

// Uploads chunks one at the time
async function upload(chunks) {
  for (let chunk of chunks) {
    await fetch('/upload', {method: 'post', body: blob})
  }
}

upload(chunks)