How can I upload files in Blazor with .NET 5

Viewed 5866

I have the following code (see below).

What I am trying to achieve is the following: I want to get the infos like size, name, etc, and after click the submit button...

In the complete application would be the possibility of deleting a file in the list before triggering the upload.

This works fine EXCEPT if I submit one file after another. I get the following exception:

Error: Microsoft.JSInterop.JSException: There is no file with ID 1. The file list may have changed. Error: There is no file with ID 1. The file list may have changed.

Could anyone help me?

Many thanks,

Raphaël

@page "/"

<h3>Upload PNG images</h3>

<p>
    <InputFile OnChange="@OnInputFileChange" multiple />
</p>

@if (imageDataUrls.Count > 0)
{
    <h4>Images</h4>

    <div class="card" style="width:30rem;">
        <div class="card-body">
            @foreach (var imageDataUrl in imageDataUrls)
            {
                <img class="rounded m-1" src="@imageDataUrl" />
            }
        </div>
    </div>
}

@if (list.Count() > 0)
{
    <div class="card" style="width:30rem;">
        <div class="card-body">
            @foreach (var file in list)
            {
                <div>@file.Name - @file.ContentType - @file.Size</div>
            }
        </div>
    </div>
}



<button @onclick="Submit">Télécharger</button>

@code {
    
    IList<string> imageDataUrls = new List<string>();
    List<IBrowserFile> list = new List<IBrowserFile>();

    void OnInputFileChange(InputFileChangeEventArgs e)
    {
        var maxAllowedFiles = 3;

        list.AddRange(e.GetMultipleFiles(maxAllowedFiles));
    }

    async Task Submit()
    {        
        var format = "image/png";
        
        foreach (var imageFile in list)
        {   
            var resizedImageFile = await imageFile.RequestImageFileAsync(format,
                100, 100);
            var buffer = new byte[resizedImageFile.Size];
            await resizedImageFile.OpenReadStream().ReadAsync(buffer);
            var imageDataUrl =
                $"data:{format};base64,{Convert.ToBase64String(buffer)}";
            imageDataUrls.Add(imageDataUrl);
        }
        
    }


}
4 Answers

I don't have a really good answer to why your solution is not working, but it is easy to fix it. It seems like you have only access to file content when you are in the event-handling method.

As a workaround, create a "ViewModel" class for your image presentation.

 public class FileViewModel
{
        /// helpful in combination with @key to make list rendering more efficient
        public Guid Id { get; set; }
        public String Name { get; set; }
        public String ContentType { get; set; }
        public Int64 Size { get; set; }
        public Byte[] Content { get; set; }
        public String ImageDataUrl { get; set; }

        public FileViewModel()
        {
            Id = Guid.NewGuid();
        }

}

Changes to OnInputFileChange

Merge your code from Submit with OnInputFileChange. During the event handling, create a ViewModel instance for each file and save the instance's content.


    List<FileViewModel> list = new List<FileViewModel>();

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        var maxAllowedFiles = 3;

        var format = "image/png";

        foreach (var imageFile in e.GetMultipleFiles(maxAllowedFiles))
        {
            var resizedImageFile = await imageFile.RequestImageFileAsync(format,
                100, 100);
            var buffer = new byte[resizedImageFile.Size];
            await resizedImageFile.OpenReadStream().ReadAsync(buffer);
            var imageDataUrl =
                $"data:{format};base64,{Convert.ToBase64String(buffer)}";

            list.Add(new FileViewModel
            {
                Content = buffer,
                ImageDataUrl = imageDataUrl,
                ContentType = imageFile.ContentType,
                Name = imageFile.Name,
                Size = imageFile.Size,
            });
        }
    }

I added a remove button to showcase a manipulation of the collection after the content is read.

delete images

@if (list.Count > 0)
{
    <h4>Images</h4>

    <div class="card" style="width:30rem;">
        <div class="card-body">
            @foreach (var file in list)
            {
                <img class="rounded m-1" @key="file.Id" src="@file.ImageDataUrl" />
                <button type="button" @onclick="() => RemoveImage(file)">Remove</button>
            }
        </div>
    </div>
}


@code {

    private void RemoveImage(FileViewModel file)
    {
        list.Remove(file);
    }
}

Using the detour, you now have full access to the data from your ViewModel instance. You can use these data to submit your file to your service then. In the example, I log the file name and length of the byte array as a proof of concept.


    async Task Submit()
    {
        foreach (var item in list)
        {
            Console.WriteLine($"file {item.Name} | size {item.Content.Length}");
        }
    }

entire component

@page "/"

<h3>Upload PNG images</h3>

<p>
    <InputFile OnChange="@OnInputFileChange" multiple />
</p>

@if (list.Count > 0)
{
    <h4>Images</h4>

    <div class="card" style="width:30rem;">
        <div class="card-body">
            @foreach (var file in list)
            {
                <img class="rounded m-1" @key="file.Id" src="@file.ImageDataUrl" />
                <button type="button" @onclick="() => RemoveImage(file)">Remove</button>
            }
        </div>
    </div>
}

@if (list.Count() > 0)
{
    <div class="card" style="width:30rem;">
        <div class="card-body">
            @foreach (var file in list)
            {
                <div>@file.Name - @file.ContentType - @file.Size</div>
            }
        </div>
    </div>
}



<button @onclick="Submit">Télécharger</button>

@code {

    List<FileViewModel> list = new List<FileViewModel>();

    private async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        var maxAllowedFiles = 3;

        var format = "image/png";

        foreach (var imageFile in e.GetMultipleFiles(maxAllowedFiles))
        {
            var resizedImageFile = await imageFile.RequestImageFileAsync(format,
                100, 100);
            var buffer = new byte[resizedImageFile.Size];
            await resizedImageFile.OpenReadStream().ReadAsync(buffer);
            var imageDataUrl =
                $"data:{format};base64,{Convert.ToBase64String(buffer)}";

            list.Add(new FileViewModel
            {
                Content = buffer,
                ImageDataUrl = imageDataUrl,
                ContentType = imageFile.ContentType,
                Name = imageFile.Name,
                Size = imageFile.Size,
            });
        }
    }

    private void RemoveImage(FileViewModel file)
    {
        list.Remove(file);
    }

    async Task Submit()
    {
        foreach (var item in list)
        {
            Console.WriteLine($"file {item.Name} | size {item.Content.Length}");
        }
    }

}

Nasty workaround. Would not recommend with large and many files. Especially in server side

Here is same issue on BlazorInputFile github How to upload multiple files on form submit

private async Task Save()
{
    foreach (var addedFile in _addedFiles)
    {
        using var stream = new MemoryStream(addedFile);
        var fileContent = new StreamContent(stream);
        var response = await new WebClient.PostAsync($"file", fileContent);
    }
}

private List<byte[]> _addedFiles = new List<byte[]>();
private async Task SingleUpload(InputFileChangeEventArgs arg)
{
    long maxFileSize = 1024 * 1024 * 15;
    var data = arg.File.OpenReadStream(maxFileSize);
    using var memoryString = new MemoryStream();
    await data.CopyToAsync(memoryString);

    _addedFiles.Add(memoryString.GetBuffer());
    model.Files.Add(new FileModel(arg.File.Name));
}

Did you have a look at the File component in Blazorise,

Once you leave the OnInputFileChange handler method, you will lose the stream to the files. So you have to Read the stream and save it somewhere before you leave. Something like this worked for me. Must be done in the OnInputFileChange event handler

List<byte[]> list = new List<byte[]>();

void OnInputFileChange(InputFileChangeEventArgs e)
{
    var maxAllowedFiles = 3;

    foreach (var imageFile in e.GetMultipleFiles(maxAllowedFiles))
    {
        var resizedImageFile = await imageFile.RequestImageFileAsync(format,100, 100);
        var buffer = new byte[resizedImageFile.Size];
        await resizedImageFile.OpenReadStream().ReadAsync(buffer);
        list.Add(buffer);
    }
}

async Task Submit()
{        
    var format = "image/png";
    
    foreach (var buffer in list)
    {   
        var imageDataUrl = $"data:{format};base64,{Convert.ToBase64String(buffer)}";
        imageDataUrls.Add(imageDataUrl);
    }
}
Related