Upload files in blazor server
In this blog post we are going to learn how to upload files in the blazor server application. I am going to upload images in this tutorial but you can upload any file (i have created reusable code).
💻Source Code: https://github.com/rd003/BlazorFile/
High level overview
We will upload images to a folder of a local machine ( on a production you have to use cloud storage) with a unique name (e.g. sd$3abccc3$1.png
), that name is going to save in database.
note: File name in the folder & database must be in sync and always be unique.
Database and ORM
- We will use
ms sql server
database anddapper
micro orm.
Database Design
create database BlazorFileDemo
use BlazorFileDemo
create table ImageFile
(
Id int primary key identity,
ImageName nvarchar(200) not null
)
Blazor server project
Create new blazor server app with a template “Blazor server app empty”
👉 Project Name :
BlazorFile
If you have created the project successfully then move ahead.
Database connectivity
In this section how we can insert record and retrieve record to/from a database.
Required packages: Install these packages from nuget
👉 Dapper
👉 Microsoft.Data.SqlClient
ConnectionString:
// appsettings.json
"ConnectionStrings": {
"default": "data source=RAVINDRA\\MSSQLSERVER01 ;initial catalog=BlazorFileDemo;integrated security=true;encrypt=false"
}
Models:
/* Models/ImageFile.cs */
namespace BlazorFile.Models
{
public class ImageFile
{
public int Id { get; set; }
public string ImageName { get; set; }
}
}
Image Upload Repository:
Create a new directory Repositories. Inside this directory create a new class ImageUploadRepository.cs.
First we will create an interface. I am going to create interface and class inside a single file , since it is a demo project. I recommend you to create it a new file with Name IImageUploadRepository.cs
// ImageUploadRepository.cs
using BlazorFile.Models;
namespace BlazorFile.Repositories;
public interface IImageUploadRepository
{
Task UploadImageToDb(ImageFile image);
Task<IEnumerable<ImageFile>> GetImages();
}
Now we will implement this interface
// ImageUploadRepository.cs
using BlazorFile.Models;
using Dapper;
using Microsoft.Data.SqlClient;
using System.Data;
namespace BlazorFile.Repositories;
// Interface
public interface IImageUploadRepository
{
Task UploadImageToDb(ImageFile image);
Task<IEnumerable<ImageFile>> GetImages();
}
// Implementation of interface
public class ImageUploadRepository: IImageUploadRepository
{
private readonly IConfiguration _config;
// key defined in the connection string in appsettings.json
const string CONN_KEY = "default";
public ImageUploadRepository(IConfiguration config)
{
_config = config;
}
public async Task UploadImageToDb(ImageFile image)
{
var connectionString = _config.GetConnectionString(CONN_KEY);
using IDbConnection connection = new SqlConnection(connectionString);
string sql = "insert into ImageFile (ImageName) values (@ImageName)";
await connection.ExecuteAsync(sql, new { ImageName = image.ImageName });
}
public async Task<IEnumerable<ImageFile>> GetImages()
{
var connectionString = _config.GetConnectionString(CONN_KEY);
using IDbConnection connection = new SqlConnection(connectionString);
string sql = "select * from ImageFile";
var images = await connection.QueryAsync<ImageFile>(sql);
return images;
}
}
Breakdown:
- Method
UploadImageToDb
will save your data to db. - Method
GetImages
will retrieve image from database.
Now add these services to DI container. Open program.cs file and add the line below
// program.cs
builder.Services.AddTransient<IImageUploadRepository,ImageUploadRepository>();
File Upload Service
Create a directory and name it Utilities.
Inside this create a interface IFileUploadService and a class FileUploadService.
// IFileUploadService.cs
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorFile.Utilities
{
public interface IFileUploadService
{
Task<(int, string)> UploadFileAsync(IBrowserFile file, int maxFileSize, string[] allowedExtensions);
}
}
Now we will implement this intrerface.
// FileUploadService.cs
using Microsoft.AspNetCore.Components.Forms;
namespace BlazorFile.Utilities;
public class FileUploadService : IFileUploadService
{
private readonly IWebHostEnvironment _environment;
public FileUploadService(IWebHostEnvironment environment, ILogger<FileUploadService> logger)
{
_environment = environment;
}
public async Task<(int, string)> UploadFileAsync(IBrowserFile file, int maxFileSize, string[] allowedExtensions)
{
var uploadDirectory = Path.Combine(_environment.WebRootPath, "Uploads");
if (!Directory.Exists(uploadDirectory))
{
Directory.CreateDirectory(uploadDirectory);
}
if (file.Size > maxFileSize)
{
return (0, $"File: {file.Name} exceeds the maximum allowed file size.");
}
var fileExtension = Path.GetExtension(file.Name);
if (!allowedExtensions.Contains(fileExtension))
{
return (0, $"File: {file.Name}, File type not allowed");
}
var fileName = $"{Guid.NewGuid()}{fileExtension}";
var path = Path.Combine(uploadDirectory, fileName);
await using var fs = new FileStream(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
return (1, fileName);
}
}
Let’s break it down.
- UploadFileAsync takes 3 params IBrowserFile file, int maxFileSize, string[] allowedExtensions and returns a tuple (int, string). I want to return status code and a message(on error)/filename(on success), that is why Ihave used tuple here.
- Files will be uploaded inside
wwwroot/Uploads.
We will check whether this directory exists or not if not then we will create it.
var uploadDirectory = Path.Combine(_environment.WebRootPath, "Uploads");
if (!Directory.Exists(uploadDirectory))
{
Directory.CreateDirectory(uploadDirectory);
}
- If file exceeds to its max size we will return with with 0 and error message
if (file.Size > maxFileSize)
{
return (0, $"File: {file.Name} exceeds the maximum allowed file size.");
}
- Retrieve the file extension and match it in allowedExtensions array.
var fileExtension = Path.GetExtension(file.Name);
if (!allowedExtensions.Contains(fileExtension))
{
return (0, $"File: {file.Name}, File type not allowed");
}
- Create a filename with GUID with file extension, so it will be unique. Open the fileStream and save it to a directory. Return (1,filename). Since we need to save this filename in database ,thats why we are returning it.
var fileName = $"{Guid.NewGuid()}{fileExtension}";
var path = Path.Combine(uploadDirectory, fileName);
await using var fs = new FileStream(path, FileMode.Create);
await file.OpenReadStream(maxFileSize).CopyToAsync(fs);
return (1, fileName);
Add this service to DI container so open program.cs file.
builder.Services.AddTransient<IFileUploadService,FileUploadService>();
Our program.cs file should like this now.
// program.cs
using BlazorFile.Repositories;
using BlazorFile.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// new line
builder.Services.AddTransient<IFileUploadService,FileUploadService>();
// new line
builder.Services.AddTransient<IImageUploadRepository,ImageUploadRepository>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Front end
Open the index.razor file add the necessary imports on the file.
@page "/"
@using BlazorFile.Models;
@using BlazorFile.Repositories;
@using BlazorFile.Utilities;
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger
@inject IFileUploadService fileService
@inject IImageUploadRepository imageUploadRepo
We are injecting Logger(basic logger with console logging), fileService and imageUploadRepo.
// Index.razor
@code
{
bool loading = false;
List<string> messages = new();
List<IBrowserFile> files = new();
int maxFileSize = 1 * 1024 * 1024;
ImageFile imageModel = new();
}
These are few fields that we need in this component.
// Index.razor
@code
{
// .....remaining code which is defined above
private async void SelectFiles(InputFileChangeEventArgs e)
{
files = e.GetMultipleFiles(maxFileSize).ToList();
}
private async Task HandleFormSubmit()
{
loading = true;
messages.Clear();
var allowedExtenstions = new string[] { ".png", ".jpg", ".jpeg", ".gif" };
int count = 0;
foreach (var file in files)
{
try
{
(int statusCode, string statusMessage) = await fileService.UploadFileAsync(file, maxFileSize, allowedExtenstions);
if (statusCode == 1)
{
messages.Add($"File : {file.Name} uploaded");
var imageData = new ImageFile
{
ImageName = statusMessage
};
// saving imagename to database
await imageUploadRepo.UploadImageToDb(imageData);
count++;
}
else
messages.Add(statusMessage);
}
catch (Exception ex)
{
messages.Add($"File : {file.Name} Error : {ex.Message}");
Logger.LogError(ex.Message);
}
}
loading = false;
messages.Add($"{count}/{files.Count} uploaded");
}
}
Add corresponding html too.
// Index.razor
<EditForm Model="@imageModel" OnValidSubmit="@HandleFormSubmit">
Select File(s):<InputFile OnChange="@SelectFiles" multiple />
<br />
<button type="submit">Upload</button>
</EditForm>
@if (loading)
{
<span>Uploading...</span>
}
<ul style="list-style:none">
@foreach (var message in messages)
{
<li>
@message
</li>
}
</ul>
- On file’s OnChange() event we are calling SelectFiles() method. In that method we are assigning all the selected files to a list named files.
- On form submit we are calling HandleFormSubmit() method.
- There, we will loop over files and inside it we will implement try/catch block.
- With the help of try/catch block, we can count the successfull uploads.
- Inside try block we will call fileService.UploadFileAsync(….) method to upload file to the wwwroot/Uploads directory. If we get
status code ==1
then we will call imageUploadRepo.UploadImageToDb(…) to save image related data to database. - Since we are uploading multiple files, we need to display multiple message. That is why we have taken a list of message. So that we can add multiple messages.
Display Images:
On the same component (Index.razor) we will display images. In index.razor , add new field images which will contain list of messages.
// Index.razor
@code
{
bool loading = false;
List<string> messages = new();
List<IBrowserFile> files = new();
int maxFileSize = 1 * 1024 * 1024;
ImageFile imageModel = new();
List<ImageFile> images = new(); // new line
// ..... remaining code
}
Now create a method to where you can fetch images data from ImageUploadRepository service.
// Index.razor
@code
{
// ... remaining code
// new code is below
private async Task LoadImages()
{
try
{
images = (await imageUploadRepo.GetImages()).ToList();
}
catch(Exception ex)
{
messages.Add("Could not load images");
}
}
}
We will override a method OnInitializedAsync(), so that we can load data when component is initialized.
// Index.razor
@code
{
// ... remaining code
// new code is below
protected override async Task OnInitializedAsync()
{
messages.Clear();
loading = true;
await LoadImages();
loading = false;
}
}
Now we need to display it with html and css.
@if(images.Count>0)
{
<h2>Images</h2>
<div style="display:flex;gap:20px;flex-wrap:wrap;">
@foreach (var image in images)
{
<img src="/Uploads/@image.ImageName" alt="image">
}
</div>
}
👉 Our final Index.razor component will look like this now.
@page "/"
@using BlazorFile.Models;
@using BlazorFile.Repositories;
@using BlazorFile.Utilities;
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.Extensions.Logging
@inject ILogger<Index> Logger
@inject IFileUploadService fileService
@inject IImageUploadRepository imageUploadRepo
<EditForm Model="@imageModel" OnValidSubmit="@HandleFormSubmit">
Select File(s):<InputFile OnChange="@SelectFiles" multiple />
<br />
<button type="submit">Upload</button>
</EditForm>
@if (loading)
{
<span>Uploading...</span>
}
<ul style="list-style:none">
@foreach (var message in messages)
{
<li>
@message
</li>
}
</ul>
@if(images.Count>0)
{
<h2>Images</h2>
<div style="display:flex;gap:20px;flex-wrap:wrap;">
@foreach (var image in images)
{
<img src="/Uploads/@image.ImageName" alt="image">
}
</div>
}
@code {
bool loading = false;
List<string> messages = new();
List<IBrowserFile> files = new();
int maxFileSize = 1 * 1024 * 1024;
ImageFile imageModel = new();
List<ImageFile> images = new();
private async void SelectFiles(InputFileChangeEventArgs e)
{
files = e.GetMultipleFiles(maxFileSize).ToList();
}
private async Task HandleFormSubmit()
{
loading = true;
messages.Clear();
var allowedExtenstions = new string[] { ".png", ".jpg", ".jpeg", ".gif" };
int count = 0;
foreach (var file in files)
{
try
{
(int statusCode, string statusMessage) = await fileService.UploadFileAsync(file, maxFileSize, allowedExtenstions);
if (statusCode == 1)
{
messages.Add($"File : {file.Name} uploaded");
var imageData = new ImageFile
{
ImageName = statusMessage
};
// saving imagename to database
await imageUploadRepo.UploadImageToDb(imageData);
images.Add(imageData);
count++;
}
else
messages.Add(statusMessage);
}
catch (Exception ex)
{
messages.Add($"File : {file.Name} Error : {ex.Message}");
Logger.LogError(ex.Message);
}
}
loading = false;
messages.Add($"{count}/{files.Count} uploaded");
}
protected override async Task OnInitializedAsync()
{
messages.Clear();
loading = true;
await LoadImages();
loading = false;
}
private async Task LoadImages()
{
try
{
images = (await imageUploadRepo.GetImages()).ToList();
}
catch(Exception ex)
{
messages.Add("Could not load images");
}
}
}
Run the code and enjoy. 😎
If you find this article useful, then consider to clap and share it. You can also follow me on YouTube, I have created lots of content related to .net core and angular.
Connect with me
👉 YouTube: https://youtube.com/@ravindradevrani
👉 Twitter: https://twitter.com/ravi_devrani
👉 GitHub: https://github.com/rd003
Become a supporter ❣️:
You can buy me a coffee 🍵 : https://www.buymeacoffee.com/ravindradevrani
Thanks a lot 🙂🙂