Upload files with drag & drop or paste from clipboard in Blazor

  • Gérald Barré

To upload a file you can use the FileUpload control. This control generates a <input type="file"> element and allows you to upload a file. To get a good user experience, you also need to support dropping files from the explorer or pasting images from the clipboard. The final result should look like this:

First, you need to create a JavaScript file to register some events to handle drag and drop events and clipboard events. The main idea is to set the inputElement.files property when a file is dropped or pasted from the clipboard. Then, you can notify Blazor that the file is ready to be uploaded by raising the change event. This way, you don't have to deal with file transfer by yourself. Indeed, Blazor already implements all the complexity required to upload files.

Let's create a new file named wwwroot/dropZone.js with the following content:

// file: wwwroot/dropZone.js
export function initializeFileDropZone(dropZoneElement, inputFile) {
    // Add a class when the user drags a file over the drop zone
    function onDragHover(e) {
        e.preventDefault();
        dropZoneElement.classList.add("hover");
    }

    function onDragLeave(e) {
        e.preventDefault();
        dropZoneElement.classList.remove("hover");
    }

    // Handle the paste and drop events
    function onDrop(e) {
        e.preventDefault();
        dropZoneElement.classList.remove("hover");

        // Set the files property of the input element and raise the change event
        inputFile.files = e.dataTransfer.files;
        const event = new Event('change', { bubbles: true });
        inputFile.dispatchEvent(event);
    }

    function onPaste(e) {
        // Set the files property of the input element and raise the change event
        inputFile.files = e.clipboardData.files;
        const event = new Event('change', { bubbles: true });
        inputFile.dispatchEvent(event);
    }

    // Register all events
    dropZoneElement.addEventListener("dragenter", onDragHover);
    dropZoneElement.addEventListener("dragover", onDragHover);
    dropZoneElement.addEventListener("dragleave", onDragLeave);
    dropZoneElement.addEventListener("drop", onDrop);
    dropZoneElement.addEventListener('paste', onPaste);

    // The returned object allows to unregister the events when the Blazor component is destroyed
    return {
        dispose: () => {
            dropZoneElement.removeEventListener('dragenter', onDragHover);
            dropZoneElement.removeEventListener('dragover', onDragHover);
            dropZoneElement.removeEventListener('dragleave', onDragLeave);
            dropZoneElement.removeEventListener("drop", onDrop);
            dropZoneElement.removeEventListener('paste', handler);
        }
    }
}

Now you can use the create the razor component:

@* file: DropZone.razor *@
@inject IJSRuntime JSRuntime
@implements IAsyncDisposable

<h1>Upload files!</h1>

<div @ref="dropZoneElement" class="drop-zone">
    <p>Drop a file or paste an image from the clipboard or select a file using the input</p>
    <InputFile OnChange="@OnChange" @ref="inputFile" />
</div>

@* Display the uploaded image *@
<img src="@src" />

@code {
    ElementReference dropZoneElement;
    InputFile inputFile;

    IJSObjectReference _module;
    IJSObjectReference _dropZoneInstance;

    string src;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            // Load the JS file
            _module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./dropZone.js");

            // Initialize the drop zone
            _dropZoneInstance = await _module.InvokeAsync<IJSObjectReference>("initializeFileDropZone", dropZoneElement, inputFile.Element);
        }
    }

    // Called when a new file is uploaded
    async Task OnChange(InputFileChangeEventArgs e)
    {
        using var stream = e.File.OpenReadStream();
        using var ms = new MemoryStream();
        await stream.CopyToAsync(ms);
        src = "data:" + e.File.ContentType + ";base64," + Convert.ToBase64String(ms.ToArray());
    }

    // Unregister the drop zone events
    public async ValueTask DisposeAsync()
    {
        if (_dropZoneInstance != null)
        {
            await _dropZoneInstance.InvokeVoidAsync("dispose");
            await _dropZoneInstance.DisposeAsync();
        }

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

You can set the style in a file named DropZone.razor.css:

.drop-zone {
    padding: 20px;
    width: 100%;
    min-height: 100px;
    border: 2px dashed #0087F7;
    border-radius: 5px;
}

.drop-zone.hover {
    border-style: solid;
}

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee