无法通过 asp.net 核心服务器端将 ~200mb 文件上传到 azure blob 存储

Posted

技术标签:

【中文标题】无法通过 asp.net 核心服务器端将 ~200mb 文件上传到 azure blob 存储【英文标题】:Cant upload file ~200mb to azure blob storage by asp.net core server side 【发布时间】:2018-10-07 06:35:27 【问题描述】:

我在 Asp.net 核心上有服务器部分,它接收 Content-Type: multipart/form-data 格式标头的文件并将其发送到流中的 azure blob 存储。但是当我发送大约 200 MB 或更多的文件时出现错误

“请求正文过大,超出最大允许限制”

在我搜索时,它可能发生在旧版本的 WindowsAzure.Storage 中,但我使用的是 9.1.1 版本。当我更深入地查看方法 UploadFromStreamAsync chank blob 时,默认为 4 MB。所以我不知道该怎么做才请求你的帮助。 我的控制器:

public async Task<IActionResult> Post(string folder)
    
        string azureBlobConnectionString = _configuration.GetConnectionString("BlobConnection");
        // Retrieve storage account from connection string.
        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(azureBlobConnectionString);

        HttpResponseUploadClass responseUploadClass = await Request.StreamFile(folder, storageAccount);
        FormValueProvider formModel = responseUploadClass.FormValueProvider;

        var viewModel = new MyViewModel();

        var bindingSuccessful = await TryUpdateModelAsync(viewModel, prefix: "",
            valueProvider: formModel);

        if (!bindingSuccessful)
        
            if (!ModelState.IsValid)
            
                return BadRequest(ModelState);
            
        

        return Ok(responseUploadClass.Url);
    

以及我将流文件流发送到 azure blob 的类

 public static async Task<HttpResponseUploadClass> StreamFile(this HttpRequest request, string folder, CloudStorageAccount blobAccount)
    
        CloudBlobClient blobClient = blobAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference(folder);
        CloudBlockBlob blockBlob = null;

        if (!MultipartRequestHelper.IsMultipartContentType(request.ContentType))
        
            throw new Exception($"Expected a multipart request, but got request.ContentType");
        
        var formAccumulator = new KeyValueAccumulator();

        var boundary = MultipartRequestHelper.GetBoundary(
            MediaTypeHeaderValue.Parse(request.ContentType),
            DefaultFormOptions.MultipartBoundaryLengthLimit);
        var reader = new MultipartReader(boundary, request.Body);

        var section = await reader.ReadNextSectionAsync();
        while (section != null)
        
            ContentDispositionHeaderValue contentDisposition;
            var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
            var disposition = ContentDispositionHeaderValue.Parse(section.ContentDisposition);
            if (hasContentDispositionHeader)
            
                if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
                
                    try
                    
                        string fileName = HttpUtility.UrlEncode(disposition.FileName.Value.Replace("\"", ""), Encoding.UTF8);
                        blockBlob = container.GetBlockBlobReference(Guid.NewGuid().ToString());
                        blockBlob.Properties.ContentType = GetMimeTypeByWindowsRegistry(fileName);
                        blockBlob.Properties.ContentDisposition = "attachment; filename*=UTF-8''" + fileName;
                        await blockBlob.UploadFromStreamAsync(section.Body);
                    
                    catch (Exception e)
                    
                        Console.WriteLine(e);
                        throw;
                    
                
                else if (MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition))
                
                    var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name);
                    var encoding = GetEncoding(section);
                    using (var streamReader = new StreamReader(
                        section.Body,
                        encoding,
                        detectEncodingFromByteOrderMarks: true,
                        bufferSize: 1024,
                        leaveOpen: true))
                    
                        var value = await streamReader.ReadToEndAsync();
                        if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase))
                        
                            value = String.Empty;
                        
                        formAccumulator.Append(key.Value, value);

                        if (formAccumulator.ValueCount > DefaultFormOptions.ValueCountLimit)
                        
                            throw new InvalidDataException($"Form key count limit DefaultFormOptions.ValueCountLimit exceeded.");
                        
                    
                
            
            section = await reader.ReadNextSectionAsync();
        
        var formValueProvider = new FormValueProvider(
            BindingSource.Form,
            new FormCollection(formAccumulator.GetResults()),
            CultureInfo.CurrentCulture);

        return new HttpResponseUploadClassFormValueProvider = formValueProvider, Url = blockBlob?.Uri.ToString();
    

【问题讨论】:

【参考方案1】:

正如您所说,每个 block blob 的大小可以不同,最大为 100 MB(使用 2016 年 5 月 31 日之前的 REST 版本的请求为 4 MB),一个块 blob 最多可以包含 50,000 个块。

如果您编写的块 Blob 大小不超过 256 MB(对于使用 2016-05-31 之前的 REST 版本的请求,则为 64 MB),您可以将其上传到 一次写入操作即可完成全部操作,请参阅Put Blob。

存储客户端默认为 32 MB 最大单块上传,可使用 SingleBlobUploadThresholdInBytes 属性设置。 当块 blob 上传大于此属性中的值时,存储客户端会将文件分成块。 您可以使用ParallelOperationThreadCount 属性设置用于并行上传块的线程数。

BlobRequestOptions requestoptions = new BlobRequestOptions()

    SingleBlobUploadThresholdInBytes = 1024 * 1024 * 50, //50MB
    ParallelOperationThreadCount = 12,
;

CloudStorageAccount account = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));
CloudBlobClient blobclient = account.CreateCloudBlobClient();
blobclient.DefaultRequestOptions = requestoptions;
CloudBlobContainer blobcontainer = blobclient.GetContainerReference("uploadfiles");
blobcontainer.CreateIfNotExists();
CloudBlockBlob blockblob = blobcontainer.GetBlockBlobReference("bigfiles");

更多详情可以参考这个thread。

【讨论】:

以上是关于无法通过 asp.net 核心服务器端将 ~200mb 文件上传到 azure blob 存储的主要内容,如果未能解决你的问题,请参考以下文章

无法在 asp.net 核心中为身份服务器 4 启用 CORS

CORS 错误,无法从 asp.net 核心后端接收数据

无法从Windows服务中托管的Asp.net核心网站启动多个Serilog接收器

一个Mini的ASP.NET Core框架的实现

如何在 asp.net core 2.1 中使用自定义消息设置状态代码?

托管在服务结构上的 ASP.Net 核心项目未启动