I am wanting to upload videos (up to 2GB) to my asp net core / 5 server from a Blazor Webassembly app.
I am successfully using IformFile for smaller files and that works fine.
I have studied a variety of sources: This post was very helpful in terms of the basics and making it work. This one is also very explanatory and gives a method of getting progress of uploading - which I want
I have also used the samples from Microsoft which uses streaming, I have built the MVC app made it work and tried it in the Blazor App, both using the Javasript Form (which works fine, but I cna't see how to get progress) and using code behind which works for smaller files, but when I try a huge 1.4GB file, it runs out of memory.
The server controller is pretty straight forward and comes from the .Net samples:
[HttpPost]
[DisableFormValueModelBinding]
[RequestSizeLimit(4294967296)]
[RequestFormLimits(MultipartBodyLengthLimit = 4294967296)]
//[ValidateAntiForgeryToken] (just for now..)
[Route("UploadFile")]
public async Task<IActionResult> UploadFile()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ModelState.AddModelError("File",
$"The request couldn't be processed (Error 1).");
// Log error
return BadRequest(ModelState);
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
_defaultFormOptions.MultipartBoundaryLengthLimit);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader =
ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition);
if (hasContentDispositionHeader)
{
....
}
// Drain any remaining section body that hasn't been consumed and
// read the headers for the next section.
section = await reader.ReadNextSectionAsync();
}
return Created(nameof(StreamingController), null);
}
In my Blazor Test Page, I have a couple of trial inputs. This one:
<h1>Upload Files</h1>
<p>
<label>
Upload up to @maxAllowedFiles files:
<InputFile OnChange="@OnInputFileChange" multiple />
</label>
</p>
@if (files.Count > 0)
{
.....
}
From a microsoft sample uses this code:
private async Task OnInputFileChange(InputFileChangeEventArgs e)
{
shouldRender = false;
var upload = false;
using var content = new MultipartFormDataContent();
foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
{
try
{
if (uploadResults.SingleOrDefault(
f => f.FileName == file.Name) is null)
{
var fileContent = new StreamContent(file.OpenReadStream(maxFileSize),BufferSize);
files.Add(
new File()
{
Name = file.Name,
});
if (file.Size < maxFileSize)
{
content.Add(
content: fileContent,
name: "\"files\"",
fileName: file.Name);
upload = true;
}
else
{
//ILogger.LogInformation("{FileName} not uploaded", file.Name);
uploadResults.Add(
new UploadResult()
{
FileName = file.Name,
ErrorCode = 6,
Uploaded = false,
});
}
}
}
catch(Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
if (upload)
{
var response = await Http.PostAsync(Routes.UploadFileRoute, content);
var newUploadResults = await response.Content
.ReadFromJsonAsync<IList<UploadResult>>();
uploadResults = uploadResults.Concat(newUploadResults).ToList();
}
shouldRender = true;
}
I also have this form from the sample app:
<h3>From SampleApp</h3>
<form id="uploadForm" action="@UploadRoute" method="post"
enctype="multipart/form-data" onsubmit="AJAXSubmit(this);return false;">
<dl>
<dt>
<label for="file">File</label>
</dt>
<dd>
<input id="file" type="file" name="file" />
</dd>
</dl>
<input class="btn" type="submit" value="Upload" />
<div style="margin-top:15px">
<output form="uploadForm" name="result"></output>
</div>
</form>
and its associated Javascript loaded straight into the wwwroot index.html (note this is straight html/javascript... no Blazor JSInterop going on here...
<script>
"use strict";
async function AJAXSubmit(oFormElement)
{
const formData = new FormData(oFormElement);
try
{
const response = await fetch(oFormElement.action, {
method: 'POST',
headers:
{
'RequestVerificationToken': getCookie('RequestVerificationToken')
},
body: formData
});
oFormElement.elements.namedItem("result").value =
'Result: ' + response.status + ' ' + response.statusText;
}
catch (error)
{
console.error('Error:', error);
}
}
function getCookie(name)
{
var value = "; " + document.cookie;
var parts = value.split("; " + name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
</script>
This is straight out of the samples from microsoft.
When I upload a 1.4GB file using the Blazor Inputfile approach (even though supposedly streamed) I can watch the the task manager and see that Chrome's memory usage (and Visual Studios) is building up until the app dies with an out of memory error. Also, having set a breakpoint on the controller entry point, it never gets to it, to break. Just a delay as some buffering is going on in the client (I guess?) and then boom. (I only have 8G Ram in my laptop which expires, it might work if I had more, but thats not the issue, it is that it is building in the browser at all)...
If I do the same thing using the form and JS, then the breakpoint is hit straight away and when continuing the memory usage of chrome increases only slightly before leveling off. VS memory still grows but not too much and I can watch the buffering work.
Is this me misunderstanding how StreamContent actually works, or is it a Blazorizm? I think I saw somewhere that the fetch was using some JS Interop or something which prevented it buffering properly? But that was in an old post pre net core 3.1 and I am using 5 now so would hope that was sorted.
I have also tried Blazorise because it seems to have some events that will allow progress monitoring, but I cant find out how to use those, since they seem to fire as a buffer is built up in the client and is over before anything hits the server. Which sort of backs up the theory above..
Thanks in advance....