How does it work?
A form is created with an input
of type="file"
, and say, a name="mylist"
// asp tag helpers are not used // so name attribute must be explicitly set <input type="file" name="mylist" required multiple accept="application/pdf, image/png" />
When a user selects the files and submits them, then the uploaded files are received as an IList of IFormFile
objects as shown below:
// IMPORTANT: the parameter "mylist" should be // of the same exact spellings as // the "name" attribute on the input element public async Task<IActionResult> OnPostAsync(List<IFormFile> mylist) { // run a foreach and // save the bytes to database or to hard-disk }
Video Explanation
Please watch the following youtube video:
Step 1 of 3: Create a Form
Write a form that contains an input of type="file"
and specifically set its name
attribute. We have to set the "name" parameter because tag helpers are not used.
IMPORTANT: Take these precautions:
-
form
must have itsenctype
set asenctype="multipart/form-data"
-
input
element must havemultiple
attribute to allow upload of multiple files. -
input
element can haveaccept
attribute to restrict the uploads to specific mime types.
@page "{handler?}" @model Modular.Pages.IndexModel <h1>Upload a PDF file < 1 MB</h1> @if (TempData.ContainsKey("Message")) { <small style="color:red"> @TempData["Message"] </small> } <form method="post" enctype="multipart/form-data"> <input type="file" name="files" required multiple accept="application/pdf, image/png" /> <p /> <input type="submit" value="Upload!" /> </form>
Step 2 of 3: Setup a Model
NOTE: it is assumed that database set up is already doneWrite a model class to hold the bytes of the uploaded file.
using Microsoft.EntityFrameworkCore; using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; namespace DBaseCon { public class UploadedFile { // auto-increment Primary ID key [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } // this column is for a file BLOB public byte[] FileBytes { get; set; } } // -------- DbContext ---------- // // dbcontext DAL - THERE IS ONE DbContext per database public class ProjectContext : DbContext { public ProjectContext(DbContextOptions options) : base(options) { // EnsureCreated is meant for testing or creating // a blank database. this code should be avoided // at this place. use some alternate program to // create/migrate your database. this code has been // used here only for tutorial purposes Database.EnsureCreated(); } // this function is used to specify FK relations, Indexes, // and [optionally] the name of your database tables // corresponding to each model protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // name of the table in your database modelBuilder.Entity<UploadedFile>().ToTable("product"); } // MUST be PLURAL public DbSet<UploadedFile> Files { get; set; } } }
Step 3 of 3: Complete the backing class
We have used TempData
for passing validation errors.
using DBaseCon; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; namespace Modular.Pages { public class IndexModel : PageModel { private readonly ProjectContext _ctx; // dependency injection of the ProductContext public IndexModel(ProjectContext ctx) { _ctx = ctx; } // handy tool for validation error [TempData] public string Message { get; set; } // handles the form post public async Task<IActionResult> OnPostAsync(List<IFormFile> mylist) { if(null != mylist) { foreach (IFormFile myfile in mylist) { if ( (myfile.Length > 1024 * 1000) || (!"application/pdf".Equals(myfile.ContentType)) ) { Message = "File must be pdf type max 1 MB. Ignored."; continue; } else { var uploadedFile = new UploadedFile(); using (MemoryStream ms = new MemoryStream()) { // copy the file to memory stream await myfile.CopyToAsync(ms); // set the byte array uploadedFile.FileBytes = ms.ToArray(); } _ctx.Files.Add(uploadedFile); await _ctx.SaveChangesAsync(); } //~else ends }//~foreach ends } return RedirectToPage(); } } }
A word of caution
The above code explains how to save a file into a database.
I would personally never do that unless the files are small, of a few bytes and not very important. Stuffing files into a single database - and that too - if the same database holds other critical data - does hold a risk of corruption, something capable of giving sleepless nights.
A better approach would be to save the files to folders on your server. They should, however, be renamed, say to some unique ID, and make sure that a virus scan sanitizes each of them. Such a scheme is safer. Moreover, a server side application can be scheduled to take a second backup.
How to save to a disk?
Modify the code like so
public async Task<IActionResult> OnPostAsync(List<IFormFile> mylist) { if(null != mylist) { foreach (IFormFile myfile in mylist) { if ( (myfile.Length > 1024 * 1000) || (!"application/pdf".Equals(myfile.ContentType)) ) { Message = "File must be pdf type max 1 MB. Ignored."; continue; } else { // generate your own name, say from an ID // or use Path.GetRandomFileName() // and store that in a database // it will keep your database small var filePath = Path.Combine("Folder", "some-name"); using (var stream = System.IO.File.Create(filePath)) { await myfile.CopyToAsync(stream); } } } } return RedirectToPage(); }
This Blog Post/Article "(C# ASP.NET Core) Uploading file(s) and Storing in a Database" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.