private async Task Save(Stream source, Stream dest, FileUploadStatus status, Action <FileUploadStatus> statusUpdate) { try { status.StartedAt = DateTime.UtcNow; if (statusUpdate != null) { statusUpdate.Invoke(status); } byte[] buffer = new byte[4096]; int bytes = 0; long totalBlocks = 0; do { bytes = await source.ReadAsync(buffer, 0, buffer.Length); await dest.WriteAsync(buffer, 0, bytes); totalBlocks += 1; status.Count += bytes; if (totalBlocks % 1024 == 0 && statusUpdate != null) { statusUpdate.Invoke(status); } } while (bytes > 0); status.Count = status.Size; status.StoppedAt = DateTime.UtcNow; _logger.LogInformation("File upload complete: {0} {1}b {2}s {3}b/s", status.Key, status.Count, status.Duration, status.Rate); } catch (Exception ex) { status.Error = ex; _logger.LogError(0, ex, "File upload failed: {0}", status.Key); throw ex; } finally { await dest.FlushAsync(); dest.Dispose(); statusUpdate.Invoke(status); //give caller chance to clean up } }
public async Task Process( HttpRequest request, Action <FormOptions> optionsAction, Func <NameValueCollection, Stream> getDestinationStream, Action <FileUploadStatus> statusUpdate, Action <NameValueCollection> postProcess = null ) { if (!request.ContentType.IsMultipartContentType()) { throw new InvalidOperationException($"Expected a multipart request, but got {request.ContentType}"); } if (optionsAction != null) { optionsAction.Invoke(_formOptions); } string boundary = request.ContentType.GetBoundary(); NameValueCollection metadata = new NameValueCollection(); MultipartReader reader = new MultipartReader(boundary, request.Body); MultipartSection section = await reader.ReadNextSectionAsync(); while (section != null) { bool hasContentDispositionHeader = ContentDispositionHeaderValue .TryParse(section.ContentDisposition, out ContentDispositionHeaderValue contentDisposition); if (hasContentDispositionHeader) { if (contentDisposition.HasFormDataContentDisposition()) { //add form values to metadata collection var encoding = section.GetEncoding(); using (var streamReader = new StreamReader( section.Body, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024, leaveOpen: true)) { // The value length limit is enforced by MultipartBodyLengthLimit string value = await streamReader.ReadToEndAsync(); if (String.Equals(value, "undefined", StringComparison.OrdinalIgnoreCase)) { value = String.Empty; } metadata = value.ParseFormValues(); } } if (contentDisposition.HasFileContentDisposition()) { metadata.Add("filename", HeaderUtilities.RemoveQuotes(contentDisposition.FileName).Value); if (!metadata["size"].HasValue()) { metadata.Add("size", "1000000000"); } if (!metadata["upload-key"].HasValue()) { metadata.Add("upload-key", Guid.NewGuid().ToString()); } Stream targetStream = getDestinationStream.Invoke(metadata); FileUploadStatus status = new FileUploadStatus { Metadata = metadata, Key = metadata["upload-key"], Size = Int64.Parse(metadata["size"]) }; await Save(section.Body, targetStream, status, statusUpdate); if (postProcess != null) { postProcess.Invoke(metadata); } } } // Drains any remaining section body that has not been consumed and // reads the headers for the next section. section = await reader.ReadNextSectionAsync(); } }