Drag and drop file uploading with .NET 5.0 Blazor InputFile component

Spread the love

One of my pet projects requires file uploading. And because I decided to create that application to be .NET 5.0 ready, it was necessary to investigate what options do we have for file uploading with .NET 5.0 Blazor applications. The first candidate to investigate is a new InputFile component, of course. So, let’s see what it can do for us.

Installing .NET 5.0 SDK

You have to download and install a fresh version of .NET 5.0 SDK. At the moment when I’m writing this post, the SDK 5.0.100-rc.1 is available. But, probably, at the moment when you read this blog, there is a fresher version of SDK, so use it.

Creating ASP.NET Core Blazor Server application project

This time I’m going to create a Blazor Server project because I want my application can upload files to somewhere and I want it can generate links to the uploaded files. It seems to me, a Blazor Server application is most convenient for this.

So, just put the next command to your console

dotnet new blazorserver -o BlazorUploads

This time my choice is the Visual Studio 16.8.0 Preview 3.2 as an IDE. But, as usual, you can use any IDE that supports developing Blazor projects.

Clean Up

Completely remove the files and directories selected on the image below.

In the NavMenu.razor component delete the next lines of code

<li class="nav-item px-3">
    <NavLink class="nav-link" href="counter">
        <span class="oi oi-plus" aria-hidden="true"></span> Counter
    </NavLink>
</li>
<li class="nav-item px-3">
    <NavLink class="nav-link" href="fetchdata">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
    </NavLink>
</li>

In the Startup.cs remove the row where the WeatherForecastService is added in the ConfigureServices() method

services.AddSingleton<WeatherForecastService>();

And finally, remove almost all content of the Index.razor page, except the first line.

@page "/" 

Editing the Index.razor page

First of all, we need to add the namespaces and services that will be used in our code. In our app, we need some staff from System.IO to save the uploaded files. Also, we need Microsoft.AspNetCore.Hosting.IWebHostEnvironment service to identify the path to save our uploaded files. So, add the next lines at the top.

@page "/"
@using System.IO;
@using Microsoft.AspNetCore.Hosting;
@inject IWebHostEnvironment _env;

Blazor HTML + C# markup

Then add the header of the page and small description

<h1>.NET 5.0 Blazor InputFile Options</h1>
<p>Investigating the <b>InputFile</b> component</p>

We’ll add two InputFile components to the Index.razor page markup. The first one will be used as a common input file control. The second one will be used for drag and drop file uploading.

<div>
    <div class="inputArea">
        <InputFile id="inputDefault"
                   OnChange="OnInputFileChange"
                   accept="image/png,image/gif,image/jpeg"/>
    </div>
    <div class="dropArea @dropClass">
        Drag and drop your files here or click to open file loading dialogue...
        <InputFile id="inputDrop"
                   OnChange="OnInputFileChange"
                   @ondragenter="HandleDragEnter"
                   @ondragleave="HandleDragLeave"
                   multiple />
    </div>

    @if (files != null && files.Count > 1)
    {
        <div>
            <ul>
                @foreach (var file in files)
                {
                    <li>@file.Name</li>
                }
            </ul>
        </div>
    }
    @if (urls.Count > 0)
    {
        foreach (var url in urls)
        {
            <br />
            <a href="@url" download>@url</a>
        }
    }
</div>

A few words about the markup code above.

<InputFile id="inputDefault"
                   OnChange="OnInputFileChange"
                   accept="image/png,image/gif,image/jpeg"/>

Here the first InputFile component added. To handle selected files, the OnInputFileChange() method is assigned to the OnChange event. That method will be declared a little bit later in a Blazor @code section. Also, this component accepts only a single png, gif, or jpeg image file.

<div class="dropArea @dropClass">
        Drag and drop your files here or click to open file loading dialogue...
        <InputFile id="inputDrop"
                   OnChange="OnInputFileChange"
                   @ondragenter="HandleDragEnter"
                   @ondragleave="HandleDragLeave"
                   multiple />
    </div>

The second InputFile control will be used to accept multiple files of any type. It’s also uses OnInputFileChange() method to handle selected files. Two additional handlers of @ondragenter and @ondragleave events are used to change the value of the dropClass variable which stores the name of a CSS class. As you have guessed, the Blazor allows us to make some tricks to change the appearance of the control on the fly. And that’s cool, indeed.

  @if (files != null && files.Count > 1)
    {
        <div>
            <ul>
                @foreach (var file in files)
                {
                    <li>@file.Name</li>
                }
            </ul>
        </div>
    } 

These lines of markup responsible for displaying the names of the files to be uploaded. The files list will be defined in the @code section later.

@if (urls.Count > 0)
    {
        foreach (var url in urls)
        {
            <br />
            <a href="@url" download>@url</a>
        }
    }
</div>

And this part of the markup just displays the links to the uploaded files. The urls list will be defined in the @code section later.

The @code section

The @code section contains few C# fields and methods.

@code {
    IReadOnlyList<IBrowserFile> files;
    List<string> urls = new List<string>();
    string dropClass = string.Empty;
    const int maxFileSize = 10485760;

    private void HandleDragEnter()
    {
        dropClass = "dropAreaDrug";
    }

    private void HandleDragLeave()
    {
        dropClass = string.Empty;
    }

    async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        dropClass = string.Empty;
        try
        {
            if (e.FileCount > 1)
            {
                files = e.GetMultipleFiles();
                urls.Clear();
                urls.AddRange(await SaveFiles(files));
            }
            else
            {
                files = null;

                var url = await SaveFile(e.File);
                urls.Clear();
                urls.Add(url);
            }
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine(ex.Message);
            throw;
        }
    }

    private async Task<List<string>> SaveFiles(IReadOnlyList<IBrowserFile> files)
    {
        var list = new List<string>();
        var guid = Guid.NewGuid().ToString();
        foreach (var file in files)
        {
            var url = await SaveFile(file, guid);
            list.Add(url);
        }
        return list;
    }

    private async Task<string> SaveFile(IBrowserFile file, string guid = null)
    {
        if (guid == null)
        {
            guid = Guid.NewGuid().ToString();
        }

        var relativePath = Path.Combine("uploads", guid);
        var dirToSave = Path.Combine(_env.WebRootPath, relativePath);
        var di = new DirectoryInfo(dirToSave);
        if (!di.Exists)
        {
            di.Create();
        }

        var filePath = Path.Combine(dirToSave, file.Name);
        using (var stream = file.OpenReadStream(maxFileSize))
        {
            using (var mstream = new MemoryStream())
            {
                await stream.CopyToAsync(mstream);
                await File.WriteAllBytesAsync(filePath, mstream.ToArray());
            }
        }

        var url = Path.Combine(relativePath, file.Name).Replace("\\", "/");
        return url;
    }
}

The files list responsible for storing the files to upload and the urls list responsible for storing the links to uploaded files. The dropClass variable grabs the name of CSS class which is applied when the user drags files on the second InputFile. And the maxFileSize constant restricts the single file size.

Note. This example is quite huge, so I didn’t do any file size validation here. When you will try to upload a file which exceeds 10MB, the exception will be raised.

The HandleDragEnter() and HandleDragLeave() handlers simply set and clear the value of the dropClass variable.

The main job is done inside the OnInputFileChange() handler. It responsible for saving single or multiple files with aid of private SaveFile() and SaveFiles() methods. The files are stored in the wwwroot/upload directory. And on each uploading, the new folder with a unique GUID name is created inside this directory.

Adding the styles

Add the next styles to the end of wwwroot/css/site.css file.

.dropArea {
    border: 2px dashed steelblue;
    padding: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: lightblue;
    font-size: 1.5rem;
    cursor: pointer;
    position: relative;
    min-height:200px;
}

    .dropArea:hover {
        background-color: lightskyblue;
        color: #333;
    }

    .dropArea input[type=file] {
        position: absolute;
        width: 100%;
        height: 100%;
        opacity: 0;
        cursor: pointer;
    }

.dropAreaDrug {
    background-color: lightseagreen;
}

The most interesting of the styles above is .dropArea input[type=file]. Here we resize the HTML input control of the second InputFile Blazor component to fill entire parent div. And we make input control transparent. So, we can see everything under it, but file dragging and dropping are handled by this control.

Final results

When you run the final code to execute, you should get something like this

As you can see, we can drag and drop any file to the blue area. Just after we dropped files, the complete list of file names appears. And a few moments later, the download links are generated.

When you click on the blue area, the dialog is shown where you can choose any type of file. And when you click on “Choose File” button (it’s our first InputFile control), only some sort of images can be selected.

So, that’s all.

As usual, the complete code of this example you can find in my GitHub repository.

Thanks for reading and happy coding!

Special thanks

Some parts of this example (CSS styles, etc.) are borrowed from the “Drag Drop File Upload Blazor” tutorial by Bradley Wells. Thanks a lot, Bradley. You saved my time :-).

Roman Simuta Blog