In this article, we are going to look at how we can upload files to a Blazor Server application using Drag & Drop, as well as Copy/Paste functionality.

We will be making use of the JavaScript Interop to invoke JavaScript functions from our C# code. Therefore, we recommend having a basic knowledge of the JSInterop in Blazor.

If you find some of the JSInterop concepts hard to understand, you can always read our article on calling JavaScript functions with C# in Blazor WebAssembly.

To download the source code for this article, you can visit our GitHub repository.

For this demo, we are going to create a Blazor Server application, so let’s start with that.

Create File Upload Component

We begin by creating a FileUpload component in the Pages folder:

@page "/fileupload"

<h3>File Upload</h3>

@code {
}

Here, we are using the @page attribute, to let Blazor know that we want this component to be routable.

Next, we want to add the input element that allows us to select a file to upload, as well as an <img> HTML element that will display our uploaded image:

<h3>File Upload </h3>

<InputFile multiple />

<img />

We make use of the InputFile component provided by the Blazor Component Library, allowing multiple files to be uploaded by using the multiple attribute.

Now we want to render an img element for each file that gets uploaded, and create an EventHandler for our InputFile component:

<InputFile OnChange="@OnChange" multiple />

<div>
    <p>@ErrorMessage</p>
</div> 

@foreach (var imageSource in imageSources)
{
    <img src="@imageSource" />
}

@code {
    private List<string> imageSources;
    private const int maxAllowedFiles = 2;
    private string ErrorMessage;
    
    async Task OnChange(InputFileChangeEventArgs e)
    {
        imageSources.Clear();
        ErrorMessage = string.Empty;

        if (e.FileCount > maxAllowedFiles)
        {
            ErrorMessage = $"Only {maxAllowedFiles} files can be uploaded";
            return;
        }

        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            using var stream = file.OpenReadStream();
            using var ms = new MemoryStream();
            await stream.CopyToAsync(ms);
            imageSources.Add($"data:{file.ContentType};base64,{Convert.ToBase64String(ms.ToArray())}");
        }
    }
}

First, we create an imageSources list, which will hold the sources of our uploaded images. We also define a maxAllowedFiles constant, to ensure we only allow a certain number of files to be uploaded. In our case, for demo purposes, we are going to allow only 2 files to be uploaded. We’ve also created an ErrorMessage field to display an error in case more than 2 files are being uploaded.

Whenever the OnChange event fires on our InputFile, we check if more than 2 files are being uploaded, and display an error message. If not, we will read the file contents for each image into a MemoryStream and finally, add the image source to our imageSources list, so that the images are displayed on the screen.

This is all we need to save and display the uploaded images. Next, we need to create a container to handle our drag and drop functionality.

Create File Drop Container

The first thing we need to do is create our fileDropContainer, which is a wrapper around our InputFile control and we are going to use to add our event listeners to:

<div @ref="fileDropContainer">
    <InputFile OnChange="@OnChange" multiple />
</div>

@code {
    ElementReference fileDropContainer
}

We create a div element, and decorate it with @ref, so that we can reference the HTML element in our @code block, assigning it to our ElementReference.

This covers the basic functionality for dragging and dropping files. Next up, we will add some CSS styling to ensure our InputFile component spans our fileDropContainer, giving us a larger area to drag and drop files onto.

Add CSS Styling to File Upload

Let’s add some CSS styling to our component to make our fileDropContainer nice and visible when dragging a file over it. We are going to create component-specific styles, by adding a FileUpload.razor.css file in our Pages folder:

.file-drop-zone {
    display: flex;
    width: 100%;
    border: 3px dotted #fb275d;
    align-items: center;
    margin-bottom: 2px;
}

.hover {
    border-style: solid;
    background-color: #00ca71;
}

.image-container {
    display: flex;
    justify-content: center;
}

.image-container img {
    width: 50%;
}

::deep input[type=file] {
    width: 100%;
    padding: 20px;
}

.error-message-container {
    color: red;
    text-align: center;
}

First, we define a hover class that we add to or remove from our fileDropContainer, as well as a file-drop-zone class. We also add an image-container class, which we will use to style our uploaded images, along with a style for the img elements inside this container.

Next, we style our InputFile component by using the ::deep combinator, selecting any input[type=file] element in child components. By default, CSS isolation only applies to the current component, but we can use the ::deep combinator to apply styles to any descendant element in child components.

Finally, we create an error-message-container class, that will style our ErrorMessage.

This CSS code is isolated. If you want to learn more about CSS isolation in Blazor, you can read our article: CSS Isolation in Blazor Applications

Next, we need to add various drag event handlers to our fileDropContainer, along with the CSS classes we just defined:

<div @ref="fileDropContainer" class="file-drop-zone @HoverClass" @ondragenter="OnDragEnter" @ondragleave="OnDragLeave" @ondragover="OnDragEnter">
    <InputFile OnChange="@OnChange" @ref="inputFile" multiple />
</div>

<div class="error-message-container">
    <p>@ErrorMessage</p>
</div>

<div class="image-container">
    @foreach (var imageSource in imageSources)
    {
        <img src="@imageSource" />
    }
</div>

@code {

    private string HoverClass;

    void OnDragEnter(DragEventArgs e) => HoverClass = "hover";

    void OnDragLeave(DragEventArgs e) => HoverClass = string.Empty;

}

We first hook up event listeners for @ondragenter, @ondragleave and @ondragover, and create two methods, OnDragEnter and OnDragLeave in our @code block. These methods simply toggle the HoverClass field between an empty string, and the hover class we defined in our CSS file. We also reference HoverClass in our fileDropContainer class list.

Then, we add the error-message-container class to the div element that wraps our ErrorMessage.

The final thing we add is a div element to wrap our images, giving them the image-container class we previously defined.

Next up, we will look at how we add our file paste functionality.

Add File Upload Copy/Paste Functionality

Currently, the ClipboardEventArgs event doesn’t give us any functionality to handle copy/paste in native Blazor code, so we need to incorporate some JavaScript.

We are going to start by creating a file in our wwwroot/js folder, called filePaste.js.

Next, we define a function called initializeFilePaste:

export function initializeFilePaste(fileDropContainer, inputFile) {

}

In this function, we export it first, so we can import it later in our FileUpload component. This function takes two parameters. The first parameter, fileDropContainer, is the container that wraps our InputFile component and is the element we add our event listener to. The second parameter is our inputFile component so that we can dispatch events that will be handled by the OnChange event in our component.

Now that we have our initialize function defined, we add the function to handle paste:

function onPaste(event) {
    inputFile.files = event.clipboardData.files;
    const changeEvent = new Event('change', { bubbles: true });
    inputFile.dispatchEvent(changeEvent);
}

When the onPaste function executes, we get the files from the clipboardData. We then dispatch a change event to our inputFile, which will call the OnChange event in our component.

Next, we add the event listener to our fileDropContainer:

fileDropContainer.addEventListener('paste', onPaste);

Finally, we return an object that contains a dispose method:

return {
    dispose: () => {
        fileDropContainer.removeEventListener('paste', onPaste);
    }
}

This allows us to dispose of the event listener when we dispose of our FileUpload component.

Invoke JavaScript Functions with JS Interop

Now that we have defined all the JavaScript we need, let’s invoke it in our FileUpload component.

The first thing we need to do is import our JavaScript function and wire up our fileDrop event listener: 

@inject IJSRuntime JSRuntime;

@code {
    IJSObjectReference _filePasteModule;
    IJSObjectReference _filePasteFunctionReference;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _filePasteModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/filePaste.js");

            _filePasteFunctionReference = await _filePasteModule.InvokeAsync<IJSObjectReference>("initializeFilePaste", fileDropContainer, inputFile.Element);
        }
    }
}

First, we inject IJSRuntime. Then, we create two IJSObjectReference fields, one to import our JavaScript module, and one to hold a reference to our initializeFilePaste function.

Next, we override the OnAfterRenderAsync component lifecycle method. If it is the first time rendering the component, we import our JavaScript file, by calling InvokeAsync and assign this reference to our _filePasteModule field.

The final step is to set up our event listeners, by invoking our initializeFilePate method, passing in our fileDropContainer first, and inputFile.Element last.

We also want to make sure to dispose of our component and JavaScript event listeners correctly:

@implements IAsyncDisposable

@code {
    public async ValueTask DisposeAsync()
    {
        if (_filePasteFunctionReference != null)
        {
            await _filePasteFunctionReference.InvokeVoidAsync("dispose");
            await _filePasteFunctionReference.DisposeAsync();
        }

        if (_filePasteModule != null)
        {
            await _filePasteModule.DisposeAsync();
        }
    }
}

First, we implement IAsyncDisposable. In the DisposeAsync method, we first invoke our dispose JavaScript function that we defined earlier. We then dispose of both _filePasteFunctionReference and _filePasteModule.

Add Routing

By using the default Blazor template, we have a NavMenu component in the Shared folder, where we can add a route to our FileUpload component:

<div class="nav-item px-3">
    <NavLink class="nav-link" href="fileupload">
        <span class="oi oi-plus" aria-hidden="true"></span> File Upload
    </NavLink>
</div>

We can simply copy one of the existing routing elements, ensuring that we set the href attribute on the NavLink component to fileupload.

Test File Upload

Now that we have all the code implemented, let’s run our application and navigate to our FileUpload component, either by selecting it from the navbar or by directly navigating to /fileupload.

First, let’s drag and drop 2 images over our fileDropContainer, which will change color as the hover class is being applied: 

drag drop file upload

We can also test our copy/paste functionality, by first copying 2 images to our clipboard, then, ensuring we have our cursor focused in the fileDropContainer, pasting our image by pressing Ctrl + V, where we should then see our image: 

copy paste file upload

Conclusion

Now we know how to create a drag & drop or copy/paste file upload in Blazor. We come across file upload in many applications,  for example when allowing users to upload an image for their user profile. By including drag & drop or copy/paste, we allow our users to more easily interact with our applications.