I previously wrote about a few posts about file uploads with ASP.NET Core, but there’s always been a web component that provides a much more excellent experience. The multiple-file drag-and-drop component allows you to upload many files in one web request because nobody has time to upload files one at a time. Single file uploads are tedious and a waste of time when you have hundreds of files and a life to live.

This post will look at the steps required to allow users to upload multiple files using a drag-and-drop HTML element. You’ll likely be surprised by how simple this approach is. Let’s get started.

The Input File HTML Element

All user-provided values in a web application depend on the input HTML element. The input tag has a type attribute that has grown to have many choices. The one you’re interested in is the type of “file”, which allows you to send files to a remote server. Luckily for you, the HTML specification allows a file input to have multiple files selected and submitted with a form post. An HTML file input already supports multiple files and allows you to drag and drop files onto the input itself.

So, you can stop reading now, right? Well, not quite. If you’ve seen the file input element styling, you know it’s a tiny target to drag files over and into it.

file upload input on HTML page

Even when successfully dragged into the element, you’ll get a vague “3 files” display text. An unclear display text isn’t a great user experience. So you want something better right?

CSS, HTML, and JavaScript For Maximum Droppage

The first goal is to think about the user experience you want to provide with the component and take advantage of the inherent behavior of the input element. So, first, you’ll create a container and stretch the input element inside that parent, making it the topmost input and invisible. Think of the input element as a transparent sheet of glass on a priceless painting. The goal will be to create an input element similar to the following.

final upgraded multiple file input element

I’m no CSS expert, so have mercy on my styling choices. But before we see some CSS, we need to structure the HTML form.

The following HTML uses ASP.NET Core Razor syntax, and I’m also using Bootstrap for some elements. Please feel free to adjust the example for your usage.

<div class="files-container">
    <form asp-page="Index" method="post" enctype="multipart/form-data">
        <div class="upload">
            <label asp-for="Uploads"></label>
            <input asp-for="Uploads" 
                   class="form-control" 
                   onchange="displayNames(this, 'names')"/>
            <ul id="names" class="d-flex justify-content-start flex-wrap"></ul>
        </div>
        <button type="submit" class="btn btn-primary">Upload</button>
    </form>
</div>

Our next step is to add some CSS to our input element. The most important part of the styling is the usage of relative and absolute. The values allow you to expand the size of the input element to its parent element. Additionally, setting the opacity to 0 of our input element means it still participates in the user experience but isn’t seen by the user.

.upload {
    position: relative;
    display: block;
    border: 1px solid black;
    background-color: #ecedee;
    padding: 3em;
    margin: 1em auto;
    border-radius: 4px;         
}
.upload label {
    border-bottom:  1px solid #ccc;
    padding: 1em;
    text-align: center;
    font-weight: bold;
    font-size:  1.3em;
}
.upload:hover {
    background-color: #86b7fe;
}
.upload input {
    position: absolute;   
    opacity: 0;       
    top: 0;
    left:  0;
    width: 100%;
    height: 100%;  
    z-index: 1;      
    padding: 0;                
}
#names li {
    margin: 1em;
}     

You may have also noticed the names element in our upload container. You’ll need to write some JavaScript to read the input files and update the contents with the file names.

<script type="text/javascript">
function displayNames(input, target) {
    const output = document.getElementById(target);
    output.innerHTML = '';
    const files = input.files        
    for (let i = 0; i < files.length; i++) {
      let file = files.item(i)
      let el = document.createElement('li');
      el.innerText = file.name;
      output.appendChild(el);
    }        
}
</script>

As mentioned before, feel free to modify the behavior and styling of this user experience to meet your needs.

The ASP.NET Core File Upload Endpoint

Similar to what you might expect with a single file upload, we need a property to bind the user input. In this sample, I’m using Razor Pages, but this would work for MVC and potentially with future versions of Minimal API endpoints. You’ll need to add a collection of IFormFile types, and ASP.NET Core model binding will handle it for you. Note, remember to set the enctype on your form to multipart/form-data, or you’ll get not files submitted.

using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace WebApplication2.Pages;

public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;

    [BindProperty, Display(Name = "Choose or Drag Files Here")]
    public List<IFormFile> Uploads { get; set; } = new();

    public IndexModel(ILogger<IndexModel> logger)
    {
        _logger = logger;
    }

    public void OnGet()
    {
    }
}

There you have it! A multiple-file upload component with just HTML, CSS, and some optional JavaScript. As a reminder, here’s what our final output looks like.

final upgraded multiple file input element

I hope you enjoyed this blog post and now have a working component to be used in your ASP.NET Core applications. Play around with the element, style it differently, and even refactor it into a reusable Razor component. As always, thank you for reading and sharing my posts.