由于请求正文过大,无法将大文件上传到ASP.NET Core 3.0 Web API失败

问题描述 投票:1回答:1

我有一个ASP.NET Core 3.0 Web API端点,已将其设置为允许发布大型音频文件。我已经按照MS docs的以下指示设置了端点。

https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-3.0#kestrel-maximum-request-body-size

[音频文件上载到终结点时,将其流式传输到Azure Blob存储容器。

我的代码在本地可以正常工作。

当我将其推送到Linux上的Azure App Service中的生产服务器时,该代码无法正常工作并出现错误,并>]

请求管道中未处理的异常:System.Net.Http.HttpRequestException:发送请求时发生错误。 ---> Microsoft.AspNetCore.Server.Kestrel.Core.BadHttpRequestException:请求正文太大。

根据以上文章的建议,我已使用以下配置增量更新的Kesterl:

.ConfigureWebHostDefaults(webBuilder =>
{
     webBuilder.UseKestrel((ctx, options) =>
     {
        var config = ctx.Configuration;

        options.Limits.MaxRequestBodySize = 6000000000;
        options.Limits.MinRequestBodyDataRate =
            new MinDataRate(bytesPerSecond: 100,
                gracePeriod: TimeSpan.FromSeconds(10));
        options.Limits.MinResponseDataRate =
            new MinDataRate(bytesPerSecond: 100,
                gracePeriod: TimeSpan.FromSeconds(10));
        options.Limits.RequestHeadersTimeout =
                TimeSpan.FromMinutes(2);
}).UseStartup<Startup>();

还配置了FormOptions以接受高达60亿的文件

services.Configure<FormOptions>(options =>
{
   options.MultipartBodyLengthLimit = 6000000000;
});

并且根据文章的建议,还使用以下属性设置API控制器

[HttpPost("audio", Name="UploadAudio")]
[DisableFormValueModelBinding]
[GenerateAntiforgeryTokenCookie]
[RequestSizeLimit(6000000000)]
[RequestFormLimits(MultipartBodyLengthLimit = 6000000000)]

最后,这是动作本身。这个巨大的代码块并不表示我希望如何编写代码,但在调试过程中,我已将其合并为一个方法。

public async Task<IActionResult> Audio()
{
    if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
    {
        throw new ArgumentException("The media file could not be processed.");
    }

    string mediaId = string.Empty;
    string instructorId = string.Empty;
    try
    {
        // process file first
        KeyValueAccumulator formAccumulator = new KeyValueAccumulator();

        var streamedFileContent = new byte[0];

        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)
        {
            var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(
                section.ContentDisposition, out var contentDisposition);

            if (hasContentDispositionHeader)
            {
                if (MultipartRequestHelper
                    .HasFileContentDisposition(contentDisposition))
                {
                    streamedFileContent =
                        await FileHelpers.ProcessStreamedFile(section, contentDisposition,
                            _permittedExtensions, _fileSizeLimit);

                }
                else if (MultipartRequestHelper
                    .HasFormDataContentDisposition(contentDisposition))
                {
                    var key = HeaderUtilities.RemoveQuotes(contentDisposition.Name).Value;
                    var encoding = FileHelpers.GetEncoding(section);

                    if (encoding == null)
                    {
                        return BadRequest($"The request could not be processed: Bad Encoding");
                    }

                    using (var streamReader = new StreamReader(
                        section.Body,
                        encoding,
                        detectEncodingFromByteOrderMarks: true,
                        bufferSize: 1024,
                        leaveOpen: true))
                    {
                        // The value length limit is enforced by 
                        // MultipartBodyLengthLimit
                        var value = await streamReader.ReadToEndAsync();

                        if (string.Equals(value, "undefined",
                            StringComparison.OrdinalIgnoreCase))
                        {
                            value = string.Empty;
                        }

                        formAccumulator.Append(key, value);

                        if (formAccumulator.ValueCount >
                            _defaultFormOptions.ValueCountLimit)
                        {
                            return BadRequest($"The request could not be processed: Key Count limit exceeded.");
                        }
                    }
                }
            }

            // Drain any remaining section body that hasn't been consumed and
            // read the headers for the next section.
            section = await reader.ReadNextSectionAsync();
        }

        var form = formAccumulator;
        var file = streamedFileContent;

        var results = form.GetResults();

        instructorId = results["instructorId"];
        string title = results["title"];
        string firstName = results["firstName"];
        string lastName = results["lastName"];
        string durationInMinutes = results["durationInMinutes"];

        //mediaId = await AddInstructorAudioMedia(instructorId, firstName, lastName, title, Convert.ToInt32(duration), DateTime.UtcNow, DateTime.UtcNow, file);

        string fileExtension = "m4a";

        // Generate Container Name - InstructorSpecific
        string containerName = $"{firstName[0].ToString().ToLower()}{lastName.ToLower()}-{instructorId}";

        string contentType = "audio/mp4";
        FileType fileType = FileType.audio;

        string authorName = $"{firstName} {lastName}";
        string authorShortName = $"{firstName[0]}{lastName}";
        string description = $"{authorShortName} - {title}";

        long duration = (Convert.ToInt32(durationInMinutes) * 60000);

        // Generate new filename
        string fileName = $"{firstName[0].ToString().ToLower()}{lastName.ToLower()}-{Guid.NewGuid()}";

        DateTime recordingDate = DateTime.UtcNow;
        DateTime uploadDate = DateTime.UtcNow;
        long blobSize = long.MinValue;
        try
        {
            // Update file properties in storage
            Dictionary<string, string> fileProperties = new Dictionary<string, string>();
            fileProperties.Add("ContentType", contentType);

            // update file metadata in storage
            Dictionary<string, string> metadata = new Dictionary<string, string>();
            metadata.Add("author", authorShortName);
            metadata.Add("tite", title);
            metadata.Add("description", description);
            metadata.Add("duration", duration.ToString());
            metadata.Add("recordingDate", recordingDate.ToString());
            metadata.Add("uploadDate", uploadDate.ToString());

            var fileNameWExt = $"{fileName}.{fileExtension}";

            var blobContainer = await _cloudStorageService.CreateBlob(containerName, fileNameWExt, "audio");

            try
            {
                MemoryStream fileContent = new MemoryStream(streamedFileContent);
                fileContent.Position = 0;
                using (fileContent)
                {
                    await blobContainer.UploadFromStreamAsync(fileContent);
                }

            }
            catch (StorageException e)
            {
                if (e.RequestInformation.HttpStatusCode == 403)
                {
                    return BadRequest(e.Message);
                }
                else
                {
                    return BadRequest(e.Message);
                }
            }

            try
            {
                foreach (var key in metadata.Keys.ToList())
                {
                    blobContainer.Metadata.Add(key, metadata[key]);
                }

                await blobContainer.SetMetadataAsync();

            }
            catch (StorageException e)
            {
                return BadRequest(e.Message);
            }

            blobSize = await StorageUtils.GetBlobSize(blobContainer);
        }
        catch (StorageException e)
        {
            return BadRequest(e.Message);
        }

        Media media = Media.Create(string.Empty, instructorId, authorName, fileName, fileType, fileExtension, recordingDate, uploadDate, ContentDetails.Create(title, description, duration, blobSize, 0, new List<string>()), StateDetails.Create(StatusType.STAGED, DateTime.MinValue, DateTime.UtcNow, DateTime.MaxValue), Manifest.Create(new Dictionary<string, string>()));

        // upload to MongoDB
        if (media != null)
        {
            var mapper = new Mapper(_mapperConfiguration);

            var dao = mapper.Map<ContentDAO>(media);

            try
            {
                await _db.Content.InsertOneAsync(dao);
            }
            catch (Exception)
            {
                mediaId = string.Empty;
            }

            mediaId = dao.Id.ToString();
        }
        else
        {
            // metadata wasn't stored, remove blob
            await _cloudStorageService.DeleteBlob(containerName, fileName, "audio");
            return BadRequest($"An issue occurred during media upload: rolling back storage change");
        }

        if (string.IsNullOrEmpty(mediaId))
        {
            return BadRequest($"Could not add instructor media");
        }

    }
    catch (Exception ex)
    {
        return BadRequest(ex.Message);
    }

    var result = new { MediaId = mediaId, InstructorId = instructorId };

    return Ok(result);
}

我重申,在本地所有这一切都很好。我不在IISExpress中运行它,而是将其作为控制台应用程序运行。

我通过SPA应用程序和邮递员提交了大型音频文件,效果很好。

我正在将此代码部署到Linux上的Azure应用服务(作为基本B1)。

由于该代码可在我的本地开发环境中使用,所以我不知道下一步要做什么。我已经重构了此代码几次,但我怀疑它与环境有关。

我找不到任何地方提到应用程序服务计划的水平是罪魁祸首,所以在我花更多的钱之前,我想看看这里是否有人遇到过这一挑战并可以提供建议。

更新:我尝试升级到生产应用服务计划,以查看是否存在未记录的入站流量门。升级也不起作用。

提前感谢。

-A

我有一个ASP.NET Core 3.0 Web API端点,已将其设置为允许发布大型音频文件。我已按照MS docs的以下指示设置了端点。 https:// docs ....

asp.net-mvc azure asp.net-core asp.net-core-webapi
1个回答
0
投票

当前,从11/2019开始,适用于Linux的Azure应用服务存在限制。它的CORS功能在默认情况下处于启用状态,无法禁用,并且它的文件大小限制似乎不会被任何已发布的Kestrel配置所覆盖。解决方案是将Web API应用程序移动到Windows的Azure应用程序服务,并且按预期方式运行。

© www.soinside.com 2019 - 2024. All rights reserved.