.NET Zero to Hero Series is now LIVE! JOIN 🚀

12 min read

File Upload in ASP.NET Core MVC - File System & Database

#dotnet

In this article, let’s go through one of the most searched queries on Google, “File Upload in ASP.NET Core MVC”. Uploading Images or other documents is a very basic and common requirement when it comes to building anything from a simple application to an enterprise-level solution. Let’s build a small application with which you could upload files of any type to a file system location or to a centralized database table.

File Upload is quite important for any kind of application, right from saving a User’s Profile Picture to storing some important documents. Files can be uploaded to either the Server’s Storage or to a Centralized Database. You can find the completed source code on my GitHub Repo.

file-upload-in-aspnet-core-mvc

In this guide, We will build together an ASP.NET Core MVC application that can upload files to both disk and database. In this way, we will get to see the entire process behind the build and add certain extra features along the way. I will be using Visual Studio 2019 Community as my IDE.

Setting up the ASP.NET Core MVC Project

Open up VS and create a new ASP.NET Core 3.1 Application with the MVC (Model-View-Controller) Templated Web Application. As our data access layer, we will use the Entity Framework Core (Code First Approach) as our ORM as it is pretty neat and efficient to set up.

File Model

We know beforehand, that we will be uploading to either the disk or to a database. Hence we will need 2 Models. These 2 models will have almost the same properties like file name, extension, created on, description, etc. These models will only vary at the following 2 properties.

  1. File Path - This property will be used by the model that is responsible to hold the details of the file that is on the disk.
  2. File Data - Whereas this property will be needed only for the model related to file in the database, as we will be converting the file to a byte array and storing this array to the data source,

Therefore, we will build an abstract class that has the common properties. Let’s call it FileModel.

Create a new class, Models/FileModel.cs. This will be the base class.

public abstract class FileModel
{
public int Id { get; set; }
public string Name { get; set; }
public string FileType { get; set; }
public string Extension { get; set; }
public string Description { get; set; }
public string UploadedBy { get; set; }
public DateTime? CreatedOn { get; set; }
}

Now, let’s create a model for the file on the file system. Name it Models/FileOnFileSystem.cs and inherit the FileModel class.

public class FileOnFileSystemModel : FileModel
{
public string FilePath { get; set; }
}

Similarly add another class for the file on database, Models/FileOnDatabaseModel.cs

public class FileOnDatabaseModel : FileModel
{
public byte[] Data { get; set; }
}

Now that we have built our models, let’s connect it to a database via Entity Framework Core.

Setting up Entity Framework Core

New to Entity Framework Core - Code First Approach?
Read the detailed guide on Getting Started with Entity Framework Core in ASP.NET Core Applications. This will cover almost everything you need to know about this awesome ORM.

First, install these packages via Package Manager Console.

Install-Package Microsoft.EntityFrameworkCore
Install-Package Microsoft.EntityFrameworkCore.Design
Install-Package Microsoft.EntityFrameworkCore.Tools
Install-Package Microsoft.EntityFrameworkCore.SqlServer

Next, add a connection string to your appsetting.json file.

"ConnectionStrings": {
"DefaultConnection": "<Your Connection String Here>"
}

With that out of of the way, let’s now configure the services. Modify the Startup.cs/ConfigureServices.

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));

Finally, let’s do the required migrations and update our database. Just run the following commands on the Package Manager Console.

add-migration Initial
update-database

You will get a done message on console. Open up SQL Server Object Explorer to check if the database and tables have been created.

file-upload-in-aspnet-core-mvc

What is IFormFile?

file-upload-in-aspnet-core-mvc

ASP.NET Core has a built in interface that facilitates file upload.

As you can see, IFormFile has several properties like Name, FileName, ContentType and a few methods to Copy the file data to a memory stream. So, the basic idea will be, a front end with a form Html tag that, on submit, click sends the list of files to a list of IFormFile interface. From here, we will do the uploading part of C#. Let’s begin by creating a new empty MVC Controller, Controllers/FileController. Now let’s add an associated view to this controller by right-clicking the index method.

file-upload-in-aspnet-core-mvc

Setting up the View and ViewModel

What’s a View Model?

To the View (the UI, Index.cshtml), we need to pass 2 models at a time. However, by default we can pass only a single model to any given view. This requirement brought about ViewModels. ViewModels are simple classes that have multiple classes and properties within them. For example, in this case, we need to pass a list of FileOnFileSystemModel and FileOnDatabaseModel to our view. Hence, we make a new class, a ViewModel class, Models/FileUploadViewModel.cs as below.

public class FileUploadViewModel
{
public List<FileOnFileSystemModel> FilesOnFileSystem { get; set; }
public List<FileOnDatabaseModel> FilesOnDatabase { get; set; }
}

After that, Let’s start modifying the View Page, Views/File/Index.cshtml.

@model FileUploadViewModel
@{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h4>Start Uploading Files Here</h4>
<hr />
@if (ViewBag.Message != null)
{
<div class="alert alert-success alert-dismissible" style="margin-top:20px">
@ViewBag.Message
</div>
}
<form method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple required />
<input type="text" autocomplete="off" placeholder="Enter File Description" name="description" required />
<button type="submit" class="btn btn-primary" asp-controller="File" asp-action="UploadToFileSystem">Upload to File System</button>
<button class="btn btn-success" type="submit" asp-controller="File" asp-action="UploadToDatabase">Upload to Database</button>
</form>

Line 1 - Here we define the available model as the created view model.
Lines 8-12 is for displaying a message/status if any exists.
Line 13 is the form tag with the method as POST and enctype attribute as multipart/form-data. PS this is mandatory for making forms that allow file upload.
Line 14 - an Input field of type file, with the name files (this name will be used in our controllers, make sure you enter this name), an attribute that says we are going to upload multiple files at once and finally a required attribute.
Line 15 - A text field for Description. Mandatory.
Line 16 and 17 are buttons of the type submit that invoke the FILE/{Method} Controller to upload the file(s) to DB/disk.

Next, let’s go through the 2 different models of file upload.

File Upload in ASP.NET Core MVC to File System

Edit the Index.cshtml and add these lines of code.

<hr />
<h4>Files on File System</h4>
@if (Model.FilesOnFileSystem.Count == 0)
{
<caption>No Records Found</caption>
}
else
{
<caption>List of Files on File System</caption>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Name</th>
<th>Description</th>
<th>File Type</th>
<th>Created On</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach (var file in Model.FilesOnFileSystem)
{
<tr>
<th>@file.Id</th>
<td>@file.Name</td>
<td>@file.Description</td>
<td>@file.FileType</td>
<td>@file.CreatedOn</td>
<td>
<a type="button" class="btn btn-primary" asp-controller="File" asp-action="DownloadFileFromFileSystem" asp-route-id="@file.Id">Download</a>
<a type="button" class="btn btn-danger" asp-controller="File" asp-action="DeleteFileFromFileSystem" asp-route-id="@file.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>
}

Line 3-6, If no records found in the FileOnFileSystem model, display a “No Records Found” message.
Line 7-38, else display the data within a bootstrap table.
Line 22-35, iterate over the list of available records and add content to the table.
Line 31,32 Add action buttons to view the file and to delete the file from the disk. These buttons will invoke a method from our File controller.

File Controller

Navigate to FileController. Make sure that you inject the ApplicaitonDbContext to the constructor of the FileController.

private readonly ApplicationDbContext context;
public FileController(ApplicationDbContext context)
{
this.context = context;
}

Initially, we will need a method that returns a View Model, FileUploadViewModel. This method will be used by the other functions in the controllers. We will call the method, LoadAllFiles.

PS, you would probably want to put such methods outside the controller, maybe in a service layer. But to keep this demonstration simple, we will add all the required functions within the controller. You could refactor the code as you would want .

private async Task<FileUploadViewModel> LoadAllFiles()
{
var viewModel = new FileUploadViewModel();
viewModel.FilesOnDatabase = await context.FilesOnDatabase.ToListAsync();
viewModel.FilesOnFileSystem = await context.FilesOnFileSystem.ToListAsync();
return viewModel;
}

Change the Index Method as follows.

public async Task<IActionResult> Index()
{
var fileuploadViewModel = await LoadAllFiles();
ViewBag.Message = TempData["Message"];
return View(fileuploadViewModel);
}

Here, we are loading all the available files and passing the View Model to the View.

Let’s get started with the actual Action Method that uploads the file(s) to the Disk. Add a new HTTPost Action method that takes in a list of IFormFile (ensure that you name it files, as we have given the same name in the html part too) and description string.

[HttpPost]
public async Task<IActionResult> UploadToFileSystem(List<IFormFile> files, string description)
{
foreach(var file in files)
{
var basePath = Path.Combine(Directory.GetCurrentDirectory() + "Files");
bool basePathExists = System.IO.Directory.Exists(basePath);
if (!basePathExists) Directory.CreateDirectory(basePath);
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
var filePath = Path.Combine(basePath, file.FileName);
var extension = Path.GetExtension(file.FileName);
if (!System.IO.File.Exists(filePath))
{
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
var fileModel = new FileOnFileSystemModel
{
CreatedOn = DateTime.UtcNow,
FileType = file.ContentType,
Extension = extension,
Name = fileName,
Description = description,
FilePath = filePath
};
context.FilesOnFileSystem.Add(fileModel);
context.SaveChanges();
}
}
TempData["Message"] = "File successfully uploaded to File System.";
return RedirectToAction("Index");
}

Line 6 - Gets the base Path, i.e, The Current Directory of the application + /Files/. Feel free to change this to your choice.
Line 7 and 8 - Checks if the base path directory exists, else creates it.
Line 9 - Gets the file name without the extension.
Line 10 - Combines the base path with the file name.
Line 11 - Gets the extension of the file. (*.png, *.mp4, etc)
Line 14-17, If the file doesnt exist in the generated path, we use a filestream object, and create a new file, and then copy the contents to it.

Line 18-26, Create a new FileOnFileSystemModel object with required values.
Line 27 and 28, Inserts this model to the db via the context instance of efcore.
Line 31 - Loads all the File data to an object.
Line 32 - Sets a message in the TempData.
Line 33 - Redirects to the Index Action Method.

Now let’s work on the 2 button click action methods, download and delete.

public async Task<IActionResult> DownloadFileFromFileSystem(int id)
{
var file = await context.FilesOnFileSystem.Where(x => x.Id == id).FirstOrDefaultAsync();
if (file == null) return null;
var memory = new MemoryStream();
using (var stream = new FileStream(file.FilePath, FileMode.Open))
{
await stream.CopyToAsync(memory);
}
memory.Position = 0;
return File(memory, file.FileType, file.Name + file.Extension);
}

DownloadFileFromFileSystem - Takes in File Id as the param, gets the corresponding records from the context instance / fileonfilesystem table. Copies the file data from the file path to a memory object, and returns the file for download/view.

public async Task<IActionResult> DeleteFileFromFileSystem(int id)
{
var file = await context.FilesOnFileSystem.Where(x => x.Id == id).FirstOrDefaultAsync();
if (file == null) return null;
if (System.IO.File.Exists(file.FilePath))
{
System.IO.File.Delete(file.FilePath);
}
context.FilesOnFileSystem.Remove(file);
context.SaveChanges();
TempData["Message"] = $"Removed {file.Name + file.Extension} successfully from File System.";
return RedirectToAction("Index");
}

DeleteFileFromFileSystem - Similarly, gets the records and deletes the file from the file system. Later we proceed to remove the corresponding record from the database as well. Finally, we redirect to the index method with a completed message.

Now let’s run our application and navigate to ../file.

file-upload-in-aspnet-core-mvc

I will upload a random file, give it a description and click on the upload to file system button.

file-upload-in-aspnet-core-mvc

So, that is done. Now let’s check the actual directory where it is supposed to be uploaded to.

file-upload-in-aspnet-core-mvc

It gets uploaded as we wanted. With that, let’s go to uploading file to the database.

File Upload in ASP.NET Core MVC to Database

Let’s add a new Action Method (POST) named UploadToDatabase that, similar to the previous method, takes in a list of iformfile and a description.

[HttpPost]
public async Task<IActionResult> UploadToDatabase(List<IFormFile> files,string description)
{
foreach (var file in files)
{
var fileName = Path.GetFileNameWithoutExtension(file.FileName);
var extension = Path.GetExtension(file.FileName);
var fileModel = new FileOnDatabaseModel
{
CreatedOn = DateTime.UtcNow,
FileType = file.ContentType,
Extension = extension,
Name = fileName,
Description = description
};
using (var dataStream = new MemoryStream())
{
await file.CopyToAsync(dataStream);
fileModel.Data = dataStream.ToArray();
}
context.FilesOnDatabase.Add(fileModel);
context.SaveChanges();
}
TempData["Message"] = "File successfully uploaded to Database";
return RedirectToAction("Index");
}

Line 16-20 , Creates a new MemoryStream object , convert file to memory object and appends ito our model’s object.
Else, this method is quite similar to our previous one.

Further Improvement.

It is possible to refactor this method and make a common method or something, just to reduce the lines of code. Or you could probably implement a design pattern here. (Might be overkill though.)

Now, add a method to Download the file from database.

public async Task<IActionResult> DownloadFileFromDatabase(int id)
{
var file = await context.FilesOnDatabase.Where(x => x.Id == id).FirstOrDefaultAsync();
if (file == null) return null;
return File(file.Data, file.FileType, file.Name+file.Extension);
}

Here, we return a file object with it’s content exactly as it is in the database. Finally we add our last method, to delete a record. It’s a quite straight forward one.

public async Task<IActionResult> DeleteFileFromDatabase(int id)
{
var file = await context.FilesOnDatabase.Where(x => x.Id == id).FirstOrDefaultAsync();
context.FilesOnDatabase.Remove(file);
context.SaveChanges();
TempData["Message"] = $"Removed {file.Name + file.Extension} successfully from Database.";
return RedirectToAction("Index");
}

With that done, let’s do our final tests. Build and run the application. Navigate to ../File/

PS, You could add a new navigation link in the nav-menu to easily navigate to the File Controller.

file-upload-in-aspnet-core-mvc

I will add some sample data and description and click on uploadtodatabase button.

file-upload-in-aspnet-core-mvc

Pretty Cool yeah :D The idea might be simple, but we have built a near to complete utility that can potentially showcase your skills as a developer, especially for the guys who are just getting started with ASP.NET Core MVC.

Summary

We have covered the most basic topic in all of ASP.NET Core MVC by building a pretty cool tool. I hope you all enjoyed this detailed guide on File Upload in ASP.NET Core MVC. Share it within your developer community to help others as well. :D Here is the source code for the completed project. Happy Coding :D

Source Code ✌️
Grab the source code of the entire implementation by clicking here. Do Follow me on GitHub .
Support ❤️
If you have enjoyed my content and code, do support me by buying a couple of coffees. This will enable me to dedicate more time to research and create new content. Cheers!
Share this Article
Share this article with your network to help others!
What's your Feedback?
Do let me know your thoughts around this article.

Mukesh's .NET Newsletter 🚀

Join 5,000+ Engineers to Boost your .NET Skills. I have started a .NET Zero to Hero Series that covers everything from the basics to advanced topics to help you with your .NET Journey! You will receive 1 Awesome Email every week.

Subscribe