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

by | Updated on Jun 20, 2020 | ASP.NET Core

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.

What we will be building?

File Upload in ASP.NET 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.

db

What is IFormFile?

iformfile

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.

add view 2

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.

first

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

first upload

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

file directory

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.

second

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

second upload

Pretty Cool yeah 😀 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.

If you found this article helpful, consider supporting,

Buy me a coffeeBuy me a coffee

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. 😀 Here is the source code for the completed project. Happy Coding 😀

13 Comments

  1. Hoshmand

    Dear Mr. Mukesh Murugan ,

    This is Hedayat Hoshmand from Kabul,

    I have found your articles very useful and easy to learn, spatially the way that you have explained the things, I appreciate your hard work to provide great learning articles, keep going on, God bless you,

    Reply
    • Mukesh Murugan

      Thanks for your feedback Hoshmand! A lot more to come 😀

      Reply
      • Hoshmand

        Great, I will follow the upcoming .Net articles, thank you

        Reply
  2. Majid Shahabfar

    Dear Mukesh,

    How about uploading a whole directory (with its subdirectories) to file system?

    Reply
    • Mukesh Murugan

      Hi Majid, Yes it is totally possible with this same approach. The only change will be that you would have to run a function like string[] subs= Directory.GetDirectories(root); which gets you all the directories within a root. (You would have to run this recursively.) After that you would have a list of filepaths and a list of directory. With this data, simply create new directories and copy the files into the locations.
      Thanks for reaching out! Stay safe

      Reply
  3. Laurentiu LAZAR

    Hi Mukesh

    Please, help me with some sugestion for combining this current article with this one: https://www.codewithmukesh.com/blog/send-emails-with-aspnet-core/
    I have to send mails with maximum 3 attachments, but also I want to save all in a database (atachments and message) . So combining your two articles may be helpful for me.

    Reply
    • Mukesh Murugan

      Hi Laurentiu, Thanks for writing.
      Here are the source codes for the projects
      1. File Upload https://github.com/iammukeshm/FileUpload.MVC
      2. Mail Service https://github.com/iammukeshm/MailService.WebApi

      Using EF Core, Create a new Table MailMessage, that has all the properties of a mail message, i.e, MailId, Body, Subject, etc. In my FileUpload project, we already are able to save the file data to the Database. In my FileModel.cs Just add a Reference Key to MailId (Each mail will have a mail id). That means, now we have a relation between the files and the mails. get it? Now all you have to do it to Save the sent mail to the database using EFCore. Hope it is clear.

      For the attachment count limitation, just handle it in the SendMailAsync Method.

      Reply
      • Laurentiu LAZAR

        Hi

        Thanks for relpy. I will check in the next days.
        In fact what bothers me is the multi file attachment. I have tried a way where I was able to save multiple attachments on file system, but as mail attachment, their content is empty.

        Reply
        • Mukesh Murugan

          You will have to transform the iformfile to attachment object using a stream reader. Probably this is why you get null data.

          Reply
  4. Thomas Owusu-Achiaw

    Dear Mukesh, Please can you assist with AspNet Identity, thus extend the identity to include a user photo, where you can retrieve and display the uploaded photo in a navbar

    Reply
    • Mukesh Murugan

      Hi Thomas, Thanks for writing.
      Are you familiar with IdentityUser class? What you will have to do, Create a new class ‘ApplicationUser’ which inherits from the IdentityUser Class. Like this, we can extend the default user class of ASPNET Identity and add new params like userImage. In the ApplicaionUser class add a property to hold image data ( public byte[] ProfilePicture { get; set; } ). Get it? Now in the services containers add this ApplicaionUser class instead of IdentityUser. This is how to handle it on the data. From the MVC end, it is pretty straight forward. Thanks

      Reply
  5. Shah

    How to do the same upload if im using angular for front end

    Reply
  6. Shah

    How to do the same upload if im using angular for front end and .net core 3.1 for back end

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Follow codewithmukesh

Support Me!

Buy me a coffeeBuy me a coffee

Pin It on Pinterest