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

 
 
  • Gérald Barré

To upload a file, you can use the FileUpload control, which renders an <input type="file"> element. For a better user experience, you should also support dropping files from the file explorer and pasting images from the clipboard. The final result looks like this:

First, create a JavaScript file to register event handlers for drag-and-drop and clipboard interactions. The approach works by setting the inputElement.files property when a file is dropped or pasted from the clipboard, then raising the change event to notify Blazor that a file is ready. This lets Blazor handle all the file transfer complexity for you.

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

wwwroot/dropZone.js (JavaScript)
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', onPaste);
        }
    }
}

Now create the Razor component:

DropZone.razor (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:

DropZone.razor.css (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?