I want to create a component in Blazor to allow users to upload a file with drag & drop or paste the image in their clipboard.
I saw a couple of posts:
- Upload files with drag & drop or paste from clipboard in Blazor
- ASP.NET Core Blazor file uploads by meziantou
- BlazorInputFile (obsolete)
In the first post the author creates a Razor page
@* 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();
}
}
}
and a javascript file to use in the page
// 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);
}
}
}
Although the post is quire recent, it doesn't work. First, because in this line
_dropZoneInstance = await _module.InvokeAsync<IJSObjectReference>
("initializeFileDropZone", dropZoneElement, inputFile.Element);
inputFile
doesn't have a property Element
. Then, the javascript has export
but it is not recognized.
How is it possible to create a similar component without using a third-part component?