我有一个ASP.NET Core 3.0 Web API端点,已将其设置为允许发布大型音频文件。我已经按照MS docs的以下指示设置了端点。
[音频文件上载到终结点时,将其流式传输到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 ....
当前,从11/2019开始,适用于Linux的Azure应用服务存在限制。它的CORS功能在默认情况下处于启用状态,无法禁用,并且它的文件大小限制似乎不会被任何已发布的Kestrel配置所覆盖。解决方案是将Web API应用程序移动到Windows的Azure应用程序服务,并且按预期方式运行。