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.
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
.
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:Â
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:Â
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.