File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages

In this article, I will explain some file upload insights and show how to upload single or multiples in the app.net razor pages application. I will cover the following points:
  • File Upload approaches: Buffering and Streaming
  • File upload security concerns
  • File Upload Storage options
  • Upload Single File in asp.net core razor pages
  • Upload Multiple files
  • Storing files in physical storage and database
 

File Upload Approaches: Buffering and Streaming

 
Generally, buffering and streaming are the two scenarios to upload files in asp.net.
 
Buffering upload files by reading entire file into an IFormFile. This upload option increases the demand of resources like disk, memory based on file size and numbers in simultaneous uploads. It is recommended for small size file. Note: the site may crash if we upload too many files using this buffering option.
 
IFromFile represents a file sent with HttpRequest.
 
Streaming is another option to upload file which reduces the utilization of resources like disk or memory during the uploads. In this option, the application send file via multipart request and the file is directly saved or processed which does not enhance the performance. It is generally used for large files.
 

Security concerns during files upload

 
File upload is always vulnerable if proper security is not applied to applications. File upload is an opportunity for attackers to upload viruses and malware, break the servers and networks security, run denial of service attacks.
 
To overcome the above issues and reduce the attacks we can take following steps
  • Always validate the file types and extension
  • Rename the file and use safe name, example use GUID for file name
  • Keep storage location into another dedicated storage and non-system storage.
  • Do not upload into app tree folder
  • Validate files in both client and server side. It is easy to break client validation.
  • Limit the file size while uploading. Always check the file size before upload
  • Only allow the required file extension (exclude like exe)
  • Do not replace or overwrite the existing file with new one
  • Use a virus/Malware scanner to the file before upload
  • Validate user before uploading files
  • Keep proper logs during file upload
  • Always grant read and write permission to the location but never execute permission.

Storage options for Uploaded File

 
Commonly, there are three storage choices for files: Physical, Database, Data Storage service
 
Physical Storage: this storage option means to store in file storage or network share. Mostly, we create a folder and store the file over there. This is quite easy to implement, however, it is always recommended to keep the logs and records in database for user access and view purposes. It is expensive compared to database storage; however, this option allows to store large files which might be restricted in database. Then again, this storage cost will be less than cloud data storage.
 
Database Storage: This option is quite convenience over the physical storage and is recommended for small files. This is economical compared to both physical and data storage service. It also easy for implementation as we can retrieve the file content from database directly.
 
Data Storage service (cloud storage service): this storage is highly scalable and recommended for large storage infrastructures. Example: Azure Blog Storage.
 
Now we will create an asp.net core application with Razor pages to implement file upload.
 
Prerequisites
  • Visual studio (I will be using visual studio 2019 for this demo)
  • .Net 5 (asp.net core 5.0)
Quickly, we will create a new asp.net core 5.0 razor pages project with Visual Studio 2019.
 
Open Visual studio >> Create a new Project as shown:
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
 
Next, Choose project template ASP.NET Core Web App.
 
A project template for creating an ASP.NET core application with ASP.NET Razor Pages content.
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
 
Please Note: Here I am selecting ASP.NET Core with Razor pages (not with MVC).
 
Next screens will give options to give the project Name, Location and solution name as shown:
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
 
Then, we will option to select the target framework. Here, we will .NET 5.0 which is a current version.
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
 
We have successfully created the ASP.NET core 5 web application with Razor pages.
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
 
We can build and run the solution.
 
Now we will proceed with File upload.
 

Upload Single File in asp.net core razor pages

 
In the solution we will modify index page. Currently, we have Index.cshtml (for View) and index.cshtml.cs (For actions or c# code)
 
We will add a model for file upload in index.cshtml.cs for file upload as:
  1. public class FileUpload  
  2. {  
  3.     [Required]  
  4.     [Display(Name = "File")]  
  5.     public IFormFile FormFile { getset; }  
  6.     public string SuccessMessage { getset; }  
  7. }  
We can keep this model in separate view model as well if you want to reuse the same model for other uploads as well. For simplicity, I am keeping in index page.
 
We will add following html in razor page i.e. index.cshtml.
  1. <form enctype="multipart/form-data" method="post">    
  2.     <div class="form-group">    
  3.         <label class="file">    
  4.             <input type="file" asp-for="fileUpload.FormFile" multiple aria-label="File browser example">    
  5.             <span class="file-custom"></span>    
  6.         </label>    
  7.         <input asp-page-handler="Upload" class="btn btn-primary" type="submit" value="Upload">    
  8.     </div>      
  9. </form>     
We need to bind the FileUpload model as well. Also we will add uplaod action as shown:
  1. [BindProperty]
  2. public FileUpload fileUpload { getset; }
  3. //upload action as shown:
  4. private string fullPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString() + "UploadImages";
  5. public IActionResult OnPostUpload(FileUpload fileUpload)
  6. {
  7.     //Creating upload folder
  8.     if (!Directory.Exists(fullPath))
  9.     {
  10.         Directory.CreateDirectory(fullPath);
  11.     }
  12.     var formFile = fileUpload.FormFile;
  13.     if (formFile.Length > 0)
  14.     {
  15.         var filePath = Path.Combine(fullPath, formFile.FileName);
  16.   
  17.         using (var stream = System.IO.File.Create(filePath))
  18.         {
  19.             formFile.CopyToAsync(stream);
  20.         }
  21.     }
  22.   
  23.     // Process uploaded files
  24.     // Don't rely on or trust the FileName property without validation.
  25.     ViewData["SuccessMessage"] = formFile.FileName.ToString() + " files uploaded!!";
  26.     return Page();
  27. }
Here, I am defining a path for file upload and creating a folder if not exist.
 
ViewData for showing uploaded file.
 
Complete index.cshtml
  1. @page
  2. @model IndexModel
  3. @{
  4.     ViewData["Title"] = "Home page";
  5. }
  6.     
  7. <div class="text-center">
  8.     <h4 >Rijsat.com | Upload Single or Multiple files</h4>
  9.     <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
  10. </div>
  11. <form enctype="multipart/form-data" method="post">
  12.     <div class="form-group">
  13.         <label class="file">
  14.             <input type="file" asp-for="fileUpload.FormFile" aria-label="File browser example">
  15.             <span class="file-custom"></span>
  16.         </label>
  17.         <input asp-page-handler="Upload" class="btn btn-primary" type="submit" value="Upload">
  18.     </div>
  19.     @{
  20.         if (ViewData["SuccessMessage"] != null)
  21.         {
  22.           <span class="badge badge-success"> @ViewData["SuccessMessage"]</span> }
  23.     }
  24. </form>
Compete index.cshtml.cs
  1. using Microsoft.AspNetCore.Http;
  2. using Microsoft.AspNetCore.Mvc;  
  3. using Microsoft.AspNetCore.Mvc.RazorPages;  
  4. using Microsoft.Extensions.Logging;  
  5. using System.ComponentModel.DataAnnotations;  
  6. using System.IO;  
  7.   
  8. namespace computervision.aspcore.Pages  
  9. {  
  10.     public class IndexModel : PageModel  
  11.     {  
  12.         private readonly ILogger<IndexModel> _logger;  
  13.         private string fullPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString() + "UploadImages";  
  14.         public IndexModel(ILogger<IndexModel> logger)  
  15.         {  
  16.             _logger = logger;  
  17.         }  
  18.         [BindProperty]  
  19.         public FileUpload fileUpload { getset; }  
  20.         public void OnGet()  
  21.         {  
  22.             ViewData["SuccessMessage"] = "";  
  23.         }  
  24.         public IActionResult OnPostUpload(FileUpload fileUpload)  
  25.         {  
  26.             //Creating upload folder  
  27.             if (!Directory.Exists(fullPath))  
  28.             {  
  29.                 Directory.CreateDirectory(fullPath);  
  30.             }  
  31.             var formFile = fileUpload.FormFile;  
  32.             var filePath = Path.Combine(fullPath, formFile.FileName);  
  33.   
  34.             using (var stream = System.IO.File.Create(filePath))  
  35.             {  
  36.                 formFile.CopyToAsync(stream);  
  37.             }           
  38.   
  39.             // Process uploaded files  
  40.             // Don't rely on or trust the FileName property without validation.  
  41.             ViewData["SuccessMessage"] = formFile.FileName.ToString() + " files uploaded!!";  
  42.             return Page();  
  43.         }          
  44.     }  
  45.     public class FileUpload  
  46.     {  
  47.         [Required]  
  48.         [Display(Name = "File")]  
  49.         public IFormFile FormFile { getset; }  
  50.         public string SuccessMessage { getset; }  
  51.     }  
  52.   
  53. }   
Now, we will run the solution and test the upload.
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
 
Select any file and click upload.
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages 
Now we can see the file uploaded successfully. Up to here, we have completed single file upload, subsequently, we will proceed for multiple files.
 
It quite easy to upload multiple files as well with some modifications.
 

Upload Multiple files

 
First, we need to modify the FileUpload Model as shown:
  1. public class FileUpload
  2. {
  3.     [Required]
  4.     [Display(Name = "File")]
  5.     public List<IFormFile> FormFiles { getset; } // convert to list
  6.     public string SuccessMessage { getset; }
  7. } 

Update IFormFile to List<iFormFile>

 
Then update razor page, add multiple in file upload input type as shown:
  1. <input type="file" asp-for="fileUpload.FormFiles" multiple aria-label="File browser example">  
Similarly, we can modify the upload functions as shown:
  1. public IActionResult OnPostUpload(FileUpload fileUpload)    
  2. {    
  3.     if (!Directory.Exists(fullPath))    
  4.     {    
  5.         Directory.CreateDirectory(fullPath);    
  6.     }    
  7.     foreach (var aformFile in fileUpload.FormFiles)    
  8.     {    
  9.         var formFile = aformFile;    
  10.         if (formFile.Length > 0)    
  11.         {    
  12.             var filePath = Path.Combine(fullPath, formFile.FileName);    
  13.   
  14.             using (var stream = System.IO.File.Create(filePath))    
  15.             {    
  16.                 formFile.CopyToAsync(stream);    
  17.             }    
  18.         }    
  19.     }    
  20.   
  21.     // Process uploaded files    
  22.     // Don't rely on or trust the FileName property without validation.    
  23.     ViewData["SuccessMessage"] = fileUpload.FormFiles.Count.ToString() + " files uploaded!!";    
  24.     return Page();    
  25. } 
For multiple files
 
Complete Index.cshtml page
  1. @page  
  2. @model IndexModel  
  3. @{  
  4.     ViewData["Title"] = "Home page";  
  5. }  
  6.   
  7. <div class="text-center">  
  8.     <h4 >Rijsat.com | Upload Single or Multiple files</h4>  
  9.     <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>  
  10. </div>  
  11. <form enctype="multipart/form-data" method="post">  
  12.     <div class="form-group">  
  13.         <label class="file">  
  14.             <input type="file" asp-for="fileUpload.FormFiles" multiple aria-label="File browser example">  
  15.             <span class="file-custom"></span>  
  16.         </label>  
  17.         <input asp-page-handler="Upload" class="btn btn-primary" type="submit" value="Upload">  
  18.     </div>     
  19.   
  20.      
  21.     @{  
  22.         if (ViewData["SuccessMessage"] != null)  
  23.         {  
  24.         <span class="badge badge-success"> @ViewData["SuccessMessage"]</span>   
  25.         }  
  26.     }  
  27. </form>  
Complete Index.cshtml.cs code.
  1. using Microsoft.AspNetCore.Http;  
  2. using Microsoft.AspNetCore.Mvc;  
  3. using Microsoft.AspNetCore.Mvc.RazorPages;  
  4. using Microsoft.Extensions.Logging;  
  5. using System.Collections.Generic;  
  6. using System.ComponentModel.DataAnnotations;  
  7. using System.IO;  
  8.   
  9. namespace computervision.aspcore.Pages  
  10. {  
  11.     public class IndexModel : PageModel  
  12.     {  
  13.         private readonly ILogger<IndexModel> _logger;  
  14.         private string fullPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString() + "UploadImages";  
  15.         public IndexModel(ILogger<IndexModel> logger)  
  16.         {  
  17.             _logger = logger;  
  18.         }  
  19.         [BindProperty]  
  20.         public FileUpload fileUpload { getset; }  
  21.         public void OnGet()  
  22.         {  
  23.             ViewData["SuccessMessage"] = "";  
  24.         }  
  25.         public IActionResult OnPostUpload(FileUpload fileUpload)  
  26.         {  
  27.             if (!Directory.Exists(fullPath))  
  28.             {  
  29.                 Directory.CreateDirectory(fullPath);  
  30.             }  
  31.             foreach (var aformFile in fileUpload.FormFiles)  
  32.             {  
  33.                 var formFile = aformFile;  
  34.                 if (formFile.Length > 0)  
  35.                 {  
  36.                     var filePath = Path.Combine(fullPath, formFile.FileName);  
  37.   
  38.                     using (var stream = System.IO.File.Create(filePath))  
  39.                     {  
  40.                         formFile.CopyToAsync(stream);  
  41.                     }  
  42.                 }  
  43.             }  
  44.   
  45.             // Process uploaded files  
  46.             // Don't rely on or trust the FileName property without validation.  
  47.             ViewData["SuccessMessage"] = fileUpload.FormFiles.Count.ToString() + " files uploaded!!";  
  48.             return Page();  
  49.         }  
  50. }  
  51.         public class FileUpload  
  52.         {  
  53.             [Required]  
  54.             [Display(Name = "File")]  
  55.             public List<IFormFile> FormFiles { getset; } // convert to list  
  56.             public string SuccessMessage { getset; }  
  57.         }  
  58.   
  59. }  
Now, again, we will run the solution and test it. This time, we will option to select multiple files and upload shown,
 
File Upload Insights And Upload Single Or Multiple Files In ASP.NET Core Razor Pages
We have completed single and multiple file upload successfully.
 

Storing files in physical storage and database

 
In the above solution, I have stored the files into physical location which is define as shown:
  1. private string fullPath = System.AppDomain.CurrentDomain.BaseDirectory.ToString() + "UploadImages";  
Additionally, if we want to save the file into SQL server, we need to create a table to save file as shown:
  1. /****** Object:  Table [dbo].[Document]    Script Date: 4/26/2021 11:33:41 PM ******/  
  2. SET ANSI_NULLS ON  
  3. GO  
  4.   
  5. SET QUOTED_IDENTIFIER ON  
  6. GO  
  7.   
  8. CREATE TABLE [dbo].[Document](  
  9.     [Id] [bigint] IDENTITY(1,1) NOT NULL,  
  10.     [FileName] [nvarchar](250) NOT NULL,  
  11.     [FileType] [varchar](100) NULL,  
  12.     [FileData] [varbinary](maxNOT NULL,  
  13.     [Created] [datetime] NOT NULL,  
  14.     [Modified] [datetime] NOT NULL,  
  15.  CONSTRAINT [PK_Document] PRIMARY KEY CLUSTERED   
  16. (  
  17.     [Id] ASC  
  18. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFFON [PRIMARY]  
  19. ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]  
  20. GO  
Then create model based on the table, then generate value accordingly.
  1. public class DocumentViewmodel    
  2. {          
  3.     public int Id { getset; }    
  4.     [MaxLength(250)]    
  5.     public string FileName { getset; }    
  6.     [MaxLength(100)]    
  7.     public string FileType { getset; }    
  8.     [MaxLength]    
  9.     public byte[] FileData { getset; }    
  10.     public DateTime Created { getset; }    
  11.     public DateTime Modified { getset; }    
  12. } 
Then you can get the model data according as shown,
  1. // File upload to database    
  2. //Get File Name    
  3. var filename = Path.GetFileName(formFile.FileName);  
  4. //Get file Extension    
  5. var fileextension = Path.GetExtension(fileName);  
  6. // concatenating  File Name and File Extension    
  7. var newfilename = String.Concat(Convert.ToString(Guid.NewGuid()), fileextension);  
  8. var documentViewmodel = new DocumentViewmodel() {  
  9.     Id = 0,  
  10.         FileName = newfilename,  
  11.         FileType = fileExtension,  
  12.         Created = DateTime.Now,  
  13.         Modified = DateTime.Now  
  14. };  
  15. using(var target = new MemoryStream()) {  
  16.     formFile.CopyTo(target);  
  17.     documentViewmodel.FileData = target.ToArray();  
  18. }  
  19. // use this documentViewmodel to save record in database    

Conclusion

 
In this article, I have explained file uploading option which are generally two types: buffering and streaming. I have, furthermore, elaborated about security concerns on file upload as well listed some tips to reduce the attacks from file upload. Additionally, I have shared some file storage options available for file upload with comprehensive comparison. Finally, I have also created an asp.net core 5.0 with razor pages to demonstrate file upload for single and multiple files into physical location and database.