Hey, you want to learn how to handle files in Angular and .NET? This article is for you. In this article, you will learn how to upload files from the client to the server and download as well using Angular and .NET.

This article includes -Upload single/multiple files to local storage -Apply file type/size validations (client/server) -Configure the upload size limit from the program.cs -Download small/large(>GB) files.

Upload Single File

  1. Server-side code
 [HttpPost]
 [Route("UploadSingleFile")]
 public async Task<IActionResult> UploadSingleFile(IFormFile file)
 {
     try
     {
         long fileSize = file.Length;   //Get file size. File size is calculated in Bytes
         string fileType = Path.GetExtension(file.FileName); //Get extension of file.

         /*Validate File Size*/
         long maxFileSizeLimit = 2 * 1024 * 1024; //2MB
         if (fileSize > maxFileSizeLimit)
         {
             return BadRequest("Max file size limit exceed.");
         }

         /*Validate File Types*/
         string[] allowedFileTypes = { ".png", ".jpg", ".pdf", ".mp3" }; //List all types of file you want to allow for upload.
         if (!allowedFileTypes.Contains(fileType))
         {
             return BadRequest("File type not matched");
         }


         var localStorageDirectory = "D:\\LocalStorage";

         //Check and If not exists create directory.
         if (!Directory.Exists(localStorageDirectory))
         {
             Directory.CreateDirectory(localStorageDirectory);
         }

         /*
           *Construct file path: fileStorageDirectory + FileName
           *!!Note: You can store this file path into database to access it while downloading or preview.
         */
         var filePath = Path.Combine(localStorageDirectory, file.FileName);


         /*
          * Create FileStream to Read/Write files.
          * FileMode.Create: Replaces if file with same new already exists else creates new.
          * FileMode.CreateNew: Throws error if file with same name already exists.
          */
         using var stream = new FileStream(filePath, FileMode.Create);

         await file.CopyToAsync(stream); //Copy file to storage location.
         return Ok("File uploaded successfully.");
     }
     catch (Exception ex) 
     { 
         return BadRequest("Failed to upload file.");
     }

2. Client-side code

<div>
    <h1>Single File Upload</h1>
    Select File: <input type="file" (change)="OnSelectFile($event)" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"><br>
    <button (click)="UploadSingleFile()">Upload</button>
</div>
export class FileHandlingComponent {
  selectedFile!: File;
  constructor(private readonly _http: HttpClient) {}

  OnSelectFile(event: any) {
    this.selectedFile = event.target.files[0];
  }

  UploadSingleFile() {
    const apiUrl = "/api/FileHandling/UploadSingleFile";
    const formData = new FormData();

    // Append the file to FormData
    // "file" MUST match the parameter name in ASP.NET Core:
    // public IActionResult UploadSingleFile(IFormFile file)
    formData.append("file", this.selectedFile);

    this._http.post(apiUrl, formData).subscribe({
      next: res => {
        console.log(res);
      }, error: err => {
        console.log(err);
      }
    })
  }
}

Upload Multiple Files

  1. Server-side code

    [HttpPost]
    [Route("UploadMultipleFiles")]
    public async Task<IActionResult> UploadMultipleFiles(List<IFormFile> files)
    {
        try
        {
            var localStorageDirectory = "D:\\LocalStorage";
            if (!Directory.Exists(localStorageDirectory))
            {
                Directory.CreateDirectory(localStorageDirectory);
            }
            foreach (var file in files)
            {
                var filePath = Path.Combine(localStorageDirectory, file.FileName);
                using var stream = new FileStream(filePath,FileMode.Create);
                await file.CopyToAsync (stream);
            }
            return Ok("Files uploaded successfully.");
        }
        catch(Exception ex) 
        {
            return BadRequest("Failed to upload files.");
        }
    }

2. Client-side code

<div>
    <h1>Multiple File Upload</h1>
    Select Files: <input type="file" (change)="OnSelectMultipleFiles($event)" multiple
        accept=".jpg,.jpeg,.png,.pdf,.doc,.docx"><br>
    <button (click)="UploadMultipleFiles()">Upload</button>
</div>
constructor(private readonly _http: HttpClient) {}

selectedFiles!: Array<File>;
  OnSelectMultipleFiles(event: Event) {
    const input = event.target as HTMLInputElement;
    if (input.files) {
      this.selectedFiles = Array.from(input.files);
    }
  }

  UploadMultipleFiles() {
    const apiUrl = "/api/FileHandling/UploadMultipleFiles"
    const formData = new FormData();
    this.selectedFiles.forEach(f => {
      formData.append("files", f);
    })

    this._http.post(apiUrl, formData).subscribe({
      next: res => {
        console.log(res);
      }, error: err => {
        console.log(err);
      }
    });
  }

Configure upload size limit Upload size limit controls the maximum file size a user can send to your server. Why is it important?

  • Prevents server overload (large files can consume memory, CPU, and bandwidth)
  • Protects against attacks (malicious users uploading huge files)
  • Improves performance by rejecting oversized files early

There are mainly 3 approaches to configure the upload file size limit.

  1. FormOptions.MultipartBodyLengthLimit (Recommended)

/*
What it controls
 - Limits multipart/form-data request size.
 - Mainly used for file uploads (`IFormFile`).
 - Applies when ASP.NET Core **parses form data**.
Scope
 - Only affects form submissions like:
     - <input type="file">
     - multipart/form-data
*/
//In program.cs file
builder.Services.Configure<FormOptions>(options =>  
{  
options.MultipartBodyLengthLimit = 10 * 1024 * 1024; //10MB
});

2. Kestrel MaxRequestBodySize

/*
What it controls
 - Limits the entire HTTP request body.
#Scope
 Applies to all requests, including:
  - JSON API requests
  - multipart form uploads
  - binary streams
  - file uploads
  - any large HTTP request
*/

//In Program.cs file
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024;
});

3. Limit Size per endpoint

[RequestSizeLimit(10 * 1024 * 1024)] //Set request size limit per API.
[HttpPost]
[Route("UploadSingleFile")]
public async Task<IActionResult> UploadSingleFile(IFormFile file)
{
 //More logic
}

Download File File download can be implemented using two approaches, depending on file size and usage requirements:

  • Byte Array Approach: Best suited for small files, as it loads the entire file into memory, which may impact performance for larger files.
  • FileStream Approach: Suitable for both small and large files, and recommended for production use due to its efficient, streaming-based processing that minimizes memory consumption.

Download Small-Size File 1. Server Side Code

/*
 * This approach is suitable for SMALL files only.
 * - The entire file is loaded into memory as a byte array.
 * - This can increase memory usage significantly for large files.
 * - Not recommended for large or streaming scenarios.
 */
[HttpGet]
[Route("DownloadSmallFile")]
public async Task<IActionResult> DownloadSmallFile()
{
    var filePath = "D:\\LocalStorage\\img.png";

    string fileName = Path.GetFileName(filePath);
    string fileType = Path.GetExtension(filePath);

    /*
      * Add custom response headers:
      * These can be accessed on the client side (e.g., Angular, JavaScript)
      * Useful for dynamically handling file name, type, or UI behavior
    */
    Response.Headers.Add("x-fileName", fileName);
    Response.Headers.Add("x-fileType", fileType);
    //Loads File in-memory
    var fileByte = System.IO.File.ReadAllBytes(filePath);

    /*
    * Return the file as a downloadable response:
    * - fileByte: actual file content
    * - Content-Type: MIME type (constructed using file extension)
    * - fileName: suggested name for download
    */
    return File(fileByte, $"application/{fileType}", fileName);
}

2. Client Side Code

<div>
    <h1>Download small file</h1>
    <button (click)="DownloadSmallFile()">Download Small File</button>
</div>

  DownloadSmallFile() {
    const apiUrl = "/api/FileHandling/DownloadSmallFile";
    this._http.get(apiUrl, { responseType: "blob", observe: "response" }).subscribe({
      next: res => {
        //Read file information from headers.
        const fileName = res.headers.get("x-fileName");
        const fileType = res.headers.get("x-fileType");

        // Step 2: Create a temporary URL for the downloaded file
        const url = window.URL.createObjectURL(res.body!);

        // Step 3: Create a hidden anchor (<a>) element
        const link = document.createElement('a');

        // Step 4: Set the URL as href
        link.href = url;

        // Step 5: Set the file name for download
        link.download = fileName ?? "";

        // Step 6: Programmatically click the link to trigger download
        link.click();

        // Step 7: Clean up memory (important for performance)
        window.URL.revokeObjectURL(url);
      }
    })
  }

Download Huge File (>GB) 1. Server Side Code


    /*
      * This approach is suitable for BOTH large and small files (Recommended).
      * - Uses streaming instead of loading the entire file into memory.
      * - Memory-efficient and scalable for large file downloads.
      * - Ideal for production scenarios (videos, large documents, backups, etc.).
      * - Starts sending data to the client as it is being read (better performance).
  */
    [HttpGet]
    [Route("DownloadLargeFile")]
    public async Task<IActionResult> DownloadLargeFile()
    {
        string filePath = "D:\\LocalStorage\\video.mp4";
        //Read File information
        string fileName = Path.GetFileName(filePath);
        string fileType = Path.GetExtension(filePath);

        // Step 2: Read file into stream (IMPORTANT: not loading full file into memory)
        var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);

        // Step 3: Return file with correct headers
        return File(stream, $"application/{fileType}", fileName);
    }

2. Client Side Code

<div class="card">
    <h1>Download Large file</h1>
    <button (click)="DownloadLargeFile()">Download Large File</button>
</div>
DownloadLargeFile() {
    // window.open triggers a browser-level navigation request  
    // '_self' means: open in the SAME TAB (no new tab/window)
    var apiUrl = "/api/FileHandling/DownloadLargeFile";;
    window.open(apiUrl, "_self");
    /* window.open()  
        ↓  
    Browser sends request  
        ↓  
    ASP.NET Core streams file  
        ↓  
    Browser download manager handles it  
        ↓  
    File saved to disk (no Angular involvement) */
  }