15

I found the BlazorInputFile library, but there are still-open PRs from October of 2019, and I am not sure whether this library is still maintained. Also, I found a couple articles in blogs about how we can upload files with JS in Blazor. While I don't want to use JS if possible, I do need to upload files using Blazor... is it possible to do so without using JavaScript?

3
  • 1
    Upload to where? because if you are using any cloud service the best is to upload directly from the client to some storage service. I did that using Azure Storage and SAS tokens and that way we avoid any chance of overloading the server Commented Apr 1, 2020 at 20:24
  • Blazor performance improvement only occurs when you are in http 1.1 where you are getting data in chunks and have to acknowledge each chunk which adds at least 0.2 seconds to each chunk transfer time. The larger the chunk size the smaller the improvement. If you are using http 1.0 stream mode where you are getting the data in one chunk there is no reason to use Blazor provided you are sufficient memory to handle the data in one chunk. Commented Apr 2, 2020 at 9:11
  • I used blazorise.com/docs/components/file in my Blazor WebAssembly project and it worked fine for me. Example : stefh.github.io/RestEase-Client-Generator Commented Jun 17, 2020 at 19:50

5 Answers 5

35

I was dabbling with installing SteveSandersonMS' repo and then realised that, as of February 2021, there is actually a native InputFile component in ASP.NET Core 5.0.

It supports uploading of single and multiple files in Blazor and is easy to use (and you don't need to add your own JS files etc.).

I used it for single file uploads - all you need to do is add the InputFile component in the Razor page:

<InputFile OnChange="@SingleUpload" />

and then in my case I needed the file in a byte array:

@code {

   private async Task SingleUpload(InputFileChangeEventArgs e)
   {
       MemoryStream ms = new MemoryStream();
       await e.File.OpenReadStream().CopyToAsync(ms);
       var bytes = ms.ToArray();
       //do something with bytes
   }
}

InputFileChangeEventArgs gives you an IReadOnlyList of IBrowserFile which you can use to get the Name, LastModified, Size and ContentType, as well as an OpenReadStream method for getting a Stream.

There is good documentation and code on how to get multiple files in the ASP.NET docs.

Your will also need to add the System.IO namespace:

@using System.IO
Sign up to request clarification or add additional context in comments.

5 Comments

Microsoft's article says to use FileStream instead of MemoryStream. Using MemoryStream "can result in performance and security problems, especially in Blazor Server."
I feel like the next step is missing here. Once I have the bytes on the client, how do I POST them to a Web API as the only body of the request?
@mfluehr FileStream is not going to do much if you are in a Blazor Wasm-application. The question doesn't specify this (wasm or server). His answer works for both.
@Patrick Szalapski. ideally you would create something like a data service, which you can inject in the .cs-part of the razor page. You would register this dataservice on your DI-container like: string apiUrl = builder.Configuration["WebApiIWantToUseOrDesigned:ApiUrl"]; /* this refers to your appsettings.json-file */ builder.Services.AddHttpClient<IDataService, DataService>(client => client.BaseAddress = new Uri(apiUrl)); That DataService gets an HttpClient injected like public class DataService(HttpClient httpClient) : IDataService {}. Within this DataService you can now define methods that
will execute httppost's, Httpget's, ... . var response = await httpClient.PostAsync(requestUri, HttpContent). Your Byte[] will end up in the HttpContent part. As said, this dataService is injected in the cs-part of the razor page, and by calling now the methods defined in the dataService you can post the files to the api.
3

As of June 2020, The best method (WA), assuming you are using a form is to use a Tewr's FileReader. Let start with the API, the post controller would be :

      public async Task<IActionResult> PostMedia(
        [FromForm] IFormFile Picture,
        [FromForm] string Focus,
        [FromForm] string ID,
        [FromForm] string Title,
        [FromForm] string FormType,
        [FromForm] string AnimalType,
        [FromForm] string Mode,
        [FromForm] string AnimalID
        )
    {
        Debug.WriteLine($"-------------------------------------{Focus}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{ID}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{Title}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{FormType}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{AnimalType}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{Mode}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{AnimalID}-----------------------------------------------");


        //check if file was fully uploaded
        if (Picture.Length == 0 || Picture == null)

            return BadRequest("Upload a new File");
        else
            return Ok ("do something with this data....") 
     }

Then the post method on the client side would be:

    public async Task PostFile()
  {
    //create content headers
    var content = new MultipartFormDataContent();
    content.Headers.ContentDisposition = new 
    System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");

    //create content
    content.Add(new StreamContent(Pic.Stream, (int)Pic.Stream.Length), "Picture", Pic.FileName);
    content.Add(new StringContent(Pic.Title), "Title");
    content.Add(new StringContent(Pic.Focus), "Focus");
    content.Add(new StringContent(Pic.ID), "ID");
    content.Add(new StringContent(Pic.FormType), "FormType");
    content.Add(new StringContent(Pic.AnimalType), "AnimalType");
    content.Add(new StringContent(Pic.Mode), "Mode");
    content.Add(new StringContent(Pic.AnimalID), "AnimalID");
    //call to the server
    var upload = await Http.PostAsync("Media",content);

    //get server response
    Pic.Message = await upload.Content.ReadAsStringAsync();
   }

Tewr File reader helps you read the file into a stream which in my case is passed to the Pic object. The reading function which is binded to the onchange of your input element in the form would be :

  public async Task ReadFile()
   {
    var file = (await fileReaderService.CreateReference(Xelement).EnumerateFilesAsync()).FirstOrDefault();

    if (file == null)  return;


    var fileInfo = await file.ReadFileInfoAsync();

    Pic.FileName = fileInfo.Name;


    // Read into RAM
    using (var memoryStream = await file.CreateMemoryStreamAsync((int)fileInfo.Size))
    {
        // Copy store image into pic object
        Pic.Stream = new MemoryStream(memoryStream.ToArray());
    }

}

Note that Xelement is ElementReference, and it used as ref on the input element in the form.

2 Comments

@jonathan, only tried it on web assembly. I haven't tried server side Blazor yet. Some might, am not sure.
While testing on .net Core 5 blazer server app, the EnumerateFiles function does not yeild any files. So this method does not seem to be working in my perspective.
1

At the current state of affairs (as 2 April 2020), you will require JS, it is inevitable.

There are two main approaches you can take:

  • get the file data in the onchange event of the input, and call C# methods by passing the byte[] to them - that's basically the file selector approach you linked where you get the file data in the Blazor app to do whatever you want with it.

  • get the file data in the onchange event of the input, and use JS to call a remote endpoint that will receive the file and do something with it (like save it on your NAS or put it in your DB). This one is an actual file upload, as opposed to a file selector.

Both approaches are similar from coding perspective - you need JS. Perhaps in a future version of Blazor we will get an <InputFile> that will do the selection so you can to uploads with C# HTTP requests.

The File Selector approach is relatively easy to implement (literally a few lines), but it does not give you a file on the server, you have to work for it a little. The File Upload approach is harder to get right. I would personally use someone else's package for either. For file uploads things like Telerik UI for Blazor can be a commercial fit, and for the simpler selectors there is already another answer that links examples. By the way, Telerik's demos also have one such example as a component implemented for some of the demos.

Comments

1

I do this by using a component and some javascript (looks like a button). Once the component and js are incorporated, you never have to worry about it again...

Here's the Upload Component (Upload.Razor):

@inject IJSRuntime JSRuntime

@if (AllowMulitple)
{
    <input id="Xinputfile00" type="file" accept="@Filter" @onchange="UploadFile" multiple hidden />
}
else
{
    <input id="Xinputfile00" type="file" accept="@Filter" @onchange="UploadFile" hidden />
}
<button class="btn btn-default" @onclick="ClickUpload">@Title</button>

@code {

    [Parameter]
    public FileData[] Files { get; set; }

    [Parameter]
    public string Filter { get; set; }

    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public bool AllowMulitple { get; set; }

    [Parameter]
    public Action Uploaded { get; set; }

    async Task UploadFile()
    {
        string[] result = await JSRuntime.InvokeAsync<string[]>("blazorExtensions.GetFileData", "Xinputfile00");
        List<FileData> results = new List<FileData>();
        foreach (string file in result)
        {
            results.Add(new FileData(file));
        }
        this.Files = results.ToArray();
        if (Uploaded != null)
        {
            Uploaded();
        }
    }

    async Task ClickUpload()
    {
        await JSRuntime.InvokeVoidAsync("blazorExtensions.InvokeClick", "Xinputfile00");
    }

    public class FileData
    {
        public string Base64 { get; set; }
        public string MIMEType { get; set; }

        public byte[] Bytes
        {
            get
            {
                return Convert.FromBase64String(this.Base64);
            }
        }

        public FileData(string data)
        {
            if (string.IsNullOrWhiteSpace(data) || !data.Contains(","))
            {
                return;
            }
            string[] alldata = data.Split(',');
            this.MIMEType = alldata[0].Remove(0, 5).Replace(";base64", "");
            this.Base64 = alldata[1];
        }

    }

Here's the javascript excerpt:

window.blazorExtensions = {

    GetFileData: async function (id) {
        var target = document.getElementById(id);
        var filesArray = Array.prototype.slice.call(target.files);
        return Promise.all(filesArray.map(window.blazorExtensions.fileToDataURL));
    },

    fileToDataURL: async function (file) {
        var reader = new FileReader();
        return new Promise(function (resolve, reject) {
            reader.onerror = function () {
                reader.abort();
                reject(new DOMException('Error occurred reading file ' + file));
            };
            reader.onload = function (event) {
                resolve(reader.result);
                console.log('resolved');
            };
            reader.readAsDataURL(file);
            console.log('returned');
        })
    },  

    InvokeClick: function (id) {
        var elem = document.getElementById(id);
        if (typeof elem.onclick == "function") {
            elem.onclick.apply(elem);
        }
        elem.click();
    },
}

And here's a calling markup sample:

<Upload @ref="upload" Filter=".xlsx" Title="Upload" AllowMulitple="false" Uploaded="DoMyExcelThingOrSomething" />

and the method it calls after upload:

    Upload upload;
    void DoMyExcelThingOrSomething()
{
    if (upload.Files.Length < 1 || string.IsNullOrWhiteSpace(upload.Files[0].Base64))
    {
        //...nothing good here...
        return;
    }
    //play with upload.Files here...
}

1 Comment

Great solution! Is there a way to load the file by name from the code?
1

For Blazor Server, the following would upload the file to the server. There's no need to have a separate API server, or to use JS code. And it converts the stream into a file.

@using System.IO
@inject IWebHostEnvironment env

@*for ibrowser*@
@using Microsoft.AspNetCore.Components.Forms;

<h1>Blazor Server File Upload</h1>

<h3>@Message</h3>

<form @onsubmit="OnSubmit">
    <InputFile OnChange="OnInputFileChange" multiple />
    <br /><br />
    <button type="submit">Upload Selected File(s)</button>
</form>

@code {
    string Message = "No file(s) selected";
    IReadOnlyList<IBrowserFile> selectedFiles;

    void OnInputFileChange(InputFileChangeEventArgs e)
    {
        selectedFiles = e.GetMultipleFiles();
        Message = $"{selectedFiles.Count} file(s) selected";
        this.StateHasChanged();
    }

    async void OnSubmit()
    {
        foreach (var file in selectedFiles)
        {
            Stream stream = file.OpenReadStream();
            var path = $"{env.WebRootPath}\\{file.Name}";
            FileStream fs = File.Create(path);
            await stream.CopyToAsync(fs);
            stream.Close();
            fs.Close();
        }
        Message = $"{selectedFiles.Count} file(s)   uploaded on server";
        this.StateHasChanged();
    }
}

(Minor edit of http://www.bipinjoshi.net/articles/06473cc7-a391-409e-948d-3752ba3b4a6c.aspx)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.