Reputation: 2850
I have the following function in my blazor webassembly app to upload large files to the REST server:
private async Task<FileModel?> UploadSingleFile(IBrowserFile file)
{
using var content = new MultipartFormDataContent();
using Stream fileStream = file.OpenReadStream(file.Size);
var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);
fileContent.Headers.ContentLength = fileStream.Length;
content.Add(fileContent, file.Name);
var response = await HttpClient.PostAsync($"File/{_selectedDataset.ID}", content);
if (response.IsSuccessStatusCode)
{
var addedFile = (await response.Content.ReadFromJsonAsync<IEnumerable<FileModel>>())?.FirstOrDefault();
return addedFile;
} // else do something
return null;
}
The function above should be able to stream large files to the API server. It works fine with moderately small files, however testing with bigger files resulted in a out-of-memory exception on the client sied. Even before hitting the server enpoint, the HttpClient.PostAsync()
method fills the clients memory, even though the stream doesn't seem to be copied somewhere by me.
I have already tried to use SendAsync()
with an HttpRequestMessage
with an TransferEncodingChunked = true
header added, but got the same result. Somewhere behind the scenes the whole data stream is copied to memory. How can I avoid this?
For completness, here is the full exception
System.OutOfMemoryException: Out of memory at System.IO.MemoryStream.set_Capacity(Int32 value) at System.IO.MemoryStream.EnsureCapacity(Int32 value) at System.IO.MemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) at System.Net.Http.HttpContent.LimitMemoryStream.Write(Byte[] buffer, Int32 offset, Int32 count) at System.IO.MemoryStream.WriteAsync(ReadOnlyMemory
1 buffer, CancellationToken cancellationToken) --- End of stack trace from previous location --- at System.IO.Stream.<CopyToAsync>g__Core|27_0(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken) at System.Net.Http.StreamToStreamCopy.<CopyAsync>g__DisposeSourceAsync|1_0(Task copyTask, Stream source) at System.Net.Http.HttpContent.<CopyToAsync>g__WaitAsync|56_0(ValueTask copyTask) at System.Net.Http.MultipartContent.SerializeToStreamAsyncCore(Stream stream, TransportContext context, CancellationToken cancellationToken) at System.Net.Http.HttpContent.LoadIntoBufferAsyncCore(Task serializeToStreamTask, MemoryStream tempBuffer) at System.Net.Http.HttpContent.<WaitAndReturnAsync>d__82
2[[System.Net.Http.HttpContent, System.Net.Http, Version=7.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a],[System.Byte[], System.Private.CoreLib, Version=7.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].MoveNext() at System.Net.Http.BrowserHttpHandler.CallFetch(HttpRequestMessage request, CancellationToken cancellationToken, Nullable1 allowAutoRedirect) at System.Net.Http.BrowserHttpHandler.<SendAsync>g__Impl|55_0(HttpRequestMessage request, CancellationToken cancellationToken, Nullable
1 allowAutoRedirect) at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.g__Core|5_0(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.AspNetCore.Components.WebAssembly.Authentication.AuthorizationMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.g__Core|5_0(HttpRequestMessage request, CancellationToken cancellationToken) at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) at VisionServiceWasm.Pages.DatasetManager.UploadSingleFile(IBrowserFile file) at VisionServiceWasm.Pages.DatasetManager.OnUploadFiles() at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task) at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
Upvotes: 3
Views: 1073
Reputation: 72040
EDIT: The below seems to be only relevant for large responses, not large requests. Will leave here in case it's useful.
This has been documented in a GitHub issue already. It's also poorly documented here.
You need to make two changes:
HttpCompletionOption.ResponseHeadersRead
.SetBrowserResponseStreamingEnabled(true)
.Both of these mean you need to do this as a full HttpRequestMessage
and SendAsync
, rather than using PostAsync
.
You are also missing a couple of using
.
private async Task<FileModel?> UploadSingleFile(IBrowserFile file)
{
using var content = new MultipartFormDataContent();
using Stream fileStream = file.OpenReadStream(file.Size);
using var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);
fileContent.Headers.ContentLength = fileStream.Length;
content.Add(fileContent, file.Name);
using var message = new HttpRequestMessage(HttpMethod.Post, $"File/{_selectedDataset.ID}");
message.Content = content;
message.SetBrowserResponseStreamingEnabled(true);
using var response = await HttpClient.SendAsync(message, HttpCompletionOption.ResponseHeadersRead);
if (response.IsSuccessStatusCode)
{
var addedFile = (await response.Content.ReadFromJsonAsync<IEnumerable<FileModel>>())?.FirstOrDefault();
return addedFile;
} // else do something
return null;
}
Consider also using a CancellationToken
to allow the client to efficiently cancel the request.
Upvotes: 1
Reputation: 82
Because you mentioned in this case the server is never event hit, so the problem should be solved in client-side.you can handle uploading huge files by chunking them in the browsers. There are some javascript library for doing that. you can check the following project:
https://github.com/Buzut/huge-uploader
Upvotes: 1