我正在寻找有关如何从面向外部的 Angular 应用程序到面向外部的 Asp 执行非缓冲大文件(千兆字节+)上传的建议。Net 5.0 Web API 端点,然后到面向内部的 Asp。网络 Web API 端点。使用ASP.Net Core 中上传文件的指导,我从 Angular 到面向外部的 API 的第一跳工作正常,但这仅提供了一半的解决方案。我能够在不消耗太多内存的情况下将 2-gig 文件上传到第一个端点(我看到消耗了大约 300MB,这有点令人担忧,但我稍后会处理)。当我尝试流式传输到内部端点时,我得到了Unexpected end of Stream,内容可能已被另一个组件读取。当我尝试处理流并写入磁盘时。
本质上,我正在寻找一种让 DMZ 充当某种传递或代理的方法,以便我可以将文件安全地流式传输并存储到内部文件存储中,而不会耗尽服务器内存资源。
下面是我的 Angular 到 DMZ 端点流程的 C#、html 和 Typescript——我不知道从那里去哪里。关于如何在不消耗太多内存或 CPU 的情况下完成这项工作的任何建议?
我的客户端代码目前非常简单
HTML
<div class="row" style="margin-bottom:15px;">
<div class="col-md-3">
<input type="file" #file placeholder="Choose file"
(change)="uploadFile(file.files)"
style="display:none;" multiple>
<button type="button" class="btn btn-success"
(click)="file.click()">Upload File</button>
</div>
<div class="col-md-4">
<span class="upload" *ngIf="progress > 0">
{{ progress }}%
</span>
<span class="upload" *ngIf="message">
{{ message }}
</span>
</div>
</div>
打字稿
public uploadFile = (files) => {
if (files.length === 0) {
return;
}
let filesToUpload : File[] = files;
const formData = new FormData();
Array.from(filesToUpload).map((file, index) => {
return formData.append('file'+index, file, file.name);
});
this.http.post('https://localhost:5001/api/upload/uploadFileToInternalAPI', formData,
{reportProgress: true, observe: 'events'})
.subscribe(event => {
if (event.type === HttpEventType.UploadProgress)
this.progress = Math.round(100 * event.loaded / event.total);
else if (event.type === HttpEventType.Response) {
this.message = 'Upload success.';
this.onUploadFinished.emit(event.body);
}
});
}
C#
这是我的 DMZ 端点 C# 代码。注意我试图在一个请求中支持多个文件。
[HttpPost("uploadFileToInternalAPI")]
[DisableFormValueModelBinding] // Passing no parameters to the method essentially does the same thing as this attribute
[RequestSizeLimit(MaxFileSize)]
[RequestFormLimits(MultipartBodyLengthLimit = MaxFileSize)]
public async Task<IActionResult> UploadFileToInternalAPI()
{
var request = HttpContext.Request;
// validation of Content-Type
if (!request.HasFormContentType ||
!MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader) ||
string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
{
return new UnsupportedMediaTypeResult();
}
// Setup to get the first section (file) from the request
var reader = new MultipartReader(mediaTypeHeader.Boundary.Value, request.Body);
var section = await reader.ReadNextSectionAsync();
// Setup MultipartFormDataContent to post to internal API
var forwardingContent = new MultipartFormDataContent();
// Loop and process each section (file) in the multipart request.
// Builds up a multi-part request to forward to the internal API
while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition
out var contentDisposition);
if (hasContentDispositionHeader && contentDisposition.DispositionType.Equals("form-data") &&
!string.IsNullOrEmpty(contentDisposition.FileName.Value))
{
// Get the filename from the section
var fileName = contentDisposition.FileName.Value.Trim('"');
// Stream content allows to pass a non-buffered stream
forwardingContent.Add(new StreamContent(section.Body), "file", fileName);
}
section = await reader.ReadNextSectionAsync();
}
// For example only -- Don't create a HttpClient like this in production
var client = new HttpClient { BaseAddress = new Uri("http://localhost:5002") };
client.DefaultRequestHeaders.Accept.Clear();
// *** Here's where things break down - I'm not sure how to
// forward the request onto the backend without consuming
// the stream content.
var response = await client.PostAsync("/internalUpload/upload", forwardingContent);
if (response.IsSuccessStatusCode)
{
return Ok();
}
// If the code runs to this location, it means that no files have been saved
return BadRequest("No files data in the request.");
}
内部 API C#
我的内部 API 本质上是相同的 C#,但不是发布到端点,而是将流写入磁盘文件共享。