File Upload and Download: Angular + ASP.NET Core Web API
For files, Angular and ASP.NET Core usually use two different HTTP patterns:
UPLOAD
Angular File
↓
FormData
↓
multipart/form-data
↓
ASP.NET Core
↓
IFormFile
DOWNLOAD
ASP.NET Core File
↓
HTTP Response
↓
Binary data
↓
Angular Blob
↓
Browser download
Full Simple Sample Program
We'll implement:
- Upload a PDF/image from Angular to ASP.NET Core.
- Save it in an
Uploadsfolder. - Download the saved file from Angular.
- Use
FormDatafor upload. - Use
IFormFilein ASP.NET Core. - Use
Blobfor download.
ASP.NET Core — Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
options.AddPolicy("AngularApp", policy =>
{
policy
.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors("AngularApp");
app.MapControllers();
app.Run();
ASP.NET Core — FilesController.cs
using Microsoft.AspNetCore.Mvc;
namespace FileApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
private readonly IWebHostEnvironment _environment;
public FilesController(IWebHostEnvironment environment)
{
_environment = environment;
}
// Upload
[HttpPost("upload")]
public async Task<IActionResult> Upload(
[FromForm] IFormFile file)
{
if (file == null || file.Length == 0)
{
return BadRequest("No file selected.");
}
var uploadFolder = Path.Combine(
_environment.ContentRootPath,
"Uploads");
if (!Directory.Exists(uploadFolder))
{
Directory.CreateDirectory(uploadFolder);
}
var fileName = Path.GetFileName(file.FileName);
var filePath = Path.Combine(
uploadFolder,
fileName);
await using var stream =
new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(stream);
return Ok(new
{
message = "File uploaded successfully.",
fileName
});
}
// Download
[HttpGet("download/{fileName}")]
public IActionResult Download(string fileName)
{
fileName = Path.GetFileName(fileName);
var filePath = Path.Combine(
_environment.ContentRootPath,
"Uploads",
fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound();
}
var bytes =
System.IO.File.ReadAllBytes(filePath);
return File(
bytes,
"application/octet-stream",
fileName);
}
}
Angular
First configure HttpClient.
app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient()
]
};
file.service.ts
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class FileService {
private http = inject(HttpClient);
private apiUrl =
'https://localhost:7001/api/files';
uploadFile(file: File): Observable<any> {
const formData = new FormData();
formData.append(
'file',
file
);
return this.http.post(
`${this.apiUrl}/upload`,
formData
);
}
downloadFile(fileName: string): Observable<Blob> {
return this.http.get(
`${this.apiUrl}/download/${encodeURIComponent(fileName)}`,
{
responseType: 'blob'
}
);
}
}
file-upload.ts
import { Component, inject } from '@angular/core';
import { FileService } from './file.service';
@Component({
selector: 'app-file-upload',
standalone: true,
templateUrl: './file-upload.html'
})
export class FileUpload {
private fileService = inject(FileService);
selectedFile: File | null = null;
onFileSelected(event: Event): void {
const input =
event.target as HTMLInputElement;
if (input.files &&
input.files.length > 0) {
this.selectedFile =
input.files[0];
}
}
upload(): void {
if (!this.selectedFile) {
return;
}
this.fileService
.uploadFile(this.selectedFile)
.subscribe({
next: (response) => {
console.log(
'Upload successful',
response
);
},
error: (error) => {
console.error(
'Upload failed',
error
);
}
});
}
download(fileName: string): void {
this.fileService
.downloadFile(fileName)
.subscribe({
next: (blob) => {
const url =
window.URL.createObjectURL(blob);
const anchor =
document.createElement('a');
anchor.href = url;
anchor.download = fileName;
anchor.click();
window.URL.revokeObjectURL(url);
},
error: (error) => {
console.error(
'Download failed',
error
);
}
});
}
}
file-upload.html
<h2>File Upload</h2>
<input
type="file"
(change)="onFileSelected($event)"
>
<button
type="button"
(click)="upload()"
[disabled]="!selectedFile">
Upload
</button>
<hr>
<h2>File Download</h2>
<button
type="button"
(click)="download('sample.pdf')">
Download sample.pdf
</button>
1. How Do You Upload Files from Angular to ASP.NET Core?
The main technologies are:
Angular
↓
File
↓
FormData
↓
HttpClient.post()
↓
multipart/form-data
↓
ASP.NET Core
↓
IFormFile
Step 1 — Select the File
HTML:
<input
type="file"
(change)="onFileSelected($event)"
>
When the user selects a file:
onFileSelected(event: Event): void {
const input =
event.target as HTMLInputElement;
if (input.files &&
input.files.length > 0) {
this.selectedFile =
input.files[0];
}
}
The browser provides the selected file as a JavaScript:
File
object.
Step 2 — Create FormData
Files aren't normally sent as a JSON DTO.
Instead, use:
FormData
Example:
const formData = new FormData();
formData.append(
'file',
file
);
Think of it as:
FormData
│
└── file
↓
sample.pdf
Step 3 — POST the FormData
return this.http.post(
`${this.apiUrl}/upload`,
formData
);
The browser sends a request similar to:
POST /api/files/upload
Content-Type:
multipart/form-data; boundary=...
The actual request body contains the binary file data.
What is multipart/form-data?
multipart/form-data is an HTTP content type commonly used when sending:
- Files
- Form fields
- Files + form data together
For example:
multipart/form-data
Part 1
Name: file
Value: sample.pdf binary data
Part 2
Name: description
Value: Product Manual
Important: Don't Manually Set Content-Type
When using FormData, don't normally do this:
const headers = {
'Content-Type': 'multipart/form-data'
};
Instead:
this.http.post(
url,
formData
);
The browser automatically sets something like:
Content-Type:
multipart/form-data; boundary=----WebKitFormBoundary...
The boundary is important for separating individual form parts.
Step 4 — ASP.NET Core Receives IFormFile
Angular sends:
formData.append(
'file',
file
);
ASP.NET Core receives:
[FromForm] IFormFile file
Example:
[HttpPost("upload")]
public async Task<IActionResult> Upload(
[FromForm] IFormFile file)
{
// ...
}
The names should correspond:
Angular
formData.append("file", file)
↑
ASP.NET Core
IFormFile file
↑
What is IFormFile?
IFormFile represents a file received in an HTTP request.
It provides information such as:
file.FileName
file.Length
file.ContentType
and methods such as:
file.CopyToAsync(stream)
Saving the File
First create a path:
var uploadFolder = Path.Combine(
_environment.ContentRootPath,
"Uploads");
For example:
ProductApi/
│
├── Controllers/
├── Program.cs
│
└── Uploads/
└── sample.pdf
Then create a file stream:
await using var stream =
new FileStream(
filePath,
FileMode.Create);
Copy the uploaded file:
await file.CopyToAsync(stream);
Upload Flow
User selects sample.pdf
↓
<input type="file">
↓
JavaScript File
↓
FormData
↓
formData.append("file", file)
↓
HttpClient.post()
↓
multipart/form-data
↓
ASP.NET Core
↓
[FromForm] IFormFile
↓
CopyToAsync()
↓
Uploads/sample.pdf
Upload File + Additional Data
A common real-world requirement is:
Upload an article image together with title/category information.
Angular can put both into FormData:
const formData = new FormData();
formData.append(
'file',
file
);
formData.append(
'title',
'Angular HttpClient'
);
formData.append(
'categoryId',
'10'
);
ASP.NET Core can receive a DTO:
public class UploadDto
{
public string Title { get; set; }
= string.Empty;
public int CategoryId { get; set; }
public IFormFile? File { get; set; }
}
Controller:
[HttpPost("upload")]
public IActionResult Upload(
[FromForm] UploadDto request)
{
var title = request.Title;
var categoryId = request.CategoryId;
var file = request.File;
return Ok();
}
This is very useful when Angular sends a file plus ordinary form values.
Upload Multiple Files
Angular:
uploadFiles(files: File[]): Observable<any> {
const formData = new FormData();
for (const file of files) {
formData.append(
'files',
file
);
}
return this.http.post(
`${this.apiUrl}/upload-multiple`,
formData
);
}
ASP.NET Core:
[HttpPost("upload-multiple")]
public IActionResult UploadMultiple(
[FromForm] List<IFormFile> files)
{
foreach (var file in files)
{
Console.WriteLine(file.FileName);
}
return Ok();
}
Mapping:
Angular
files
files
files
↓
ASP.NET Core
List<IFormFile>
2. How Do You Download Files from ASP.NET Core?
Download is essentially the reverse process:
ASP.NET Core
↓
File
↓
HTTP Response
↓
Angular HttpClient
↓
Blob
↓
Browser
↓
Save File
ASP.NET Core Download Endpoint
[HttpGet("download/{fileName}")]
public IActionResult Download(string fileName)
{
fileName = Path.GetFileName(fileName);
var filePath = Path.Combine(
_environment.ContentRootPath,
"Uploads",
fileName);
if (!System.IO.File.Exists(filePath))
{
return NotFound();
}
var bytes =
System.IO.File.ReadAllBytes(filePath);
return File(
bytes,
"application/octet-stream",
fileName);
}
The important part is:
return File(
bytes,
"application/octet-stream",
fileName);
ASP.NET Core sends the file as the HTTP response.
What is application/octet-stream?
It is a generic MIME type representing binary data.
application/octet-stream
It can be used when you want the browser to treat the response as a downloadable binary file.
When you know the actual MIME type, you can use a more specific type.
For PDF:
return File(
bytes,
"application/pdf",
fileName);
For PNG:
return File(
bytes,
"image/png",
fileName);
For Excel:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Angular Download Service
The critical Angular code is:
downloadFile(
fileName: string
): Observable<Blob> {
return this.http.get(
`${this.apiUrl}/download/${encodeURIComponent(fileName)}`,
{
responseType: 'blob'
}
);
}
The important setting is:
responseType: 'blob'
What is a Blob?
Blob means Binary Large Object in the browser API context.
It represents raw binary-like data such as:
PDF
Image
Excel
Word
ZIP
Other files
Normally, when Angular calls an API, we expect JSON:
this.http.get<Product[]>(url);
For a file, we're not expecting JSON.
We're expecting binary data:
this.http.get(
url,
{
responseType: 'blob'
}
);
Triggering the Browser Download
After receiving the Blob:
const url =
window.URL.createObjectURL(blob);
This creates a temporary browser URL for the binary data.
Then:
const anchor =
document.createElement('a');
Set:
anchor.href = url;
anchor.download = fileName;
Then:
anchor.click();
Finally:
window.URL.revokeObjectURL(url);
This releases the temporary object URL.
Complete Download Flow
User clicks Download
↓
Angular
↓
downloadFile("sample.pdf")
↓
HttpClient.get()
↓
responseType: "blob"
↓
GET /api/files/download/sample.pdf
↓
ASP.NET Core
↓
Find file
↓
File(...)
↓
Binary HTTP response
↓
Angular Blob
↓
createObjectURL()
↓
<a download>
↓
anchor.click()
↓
Browser downloads sample.pdf
JSON vs File Upload vs File Download
This distinction is important.
Normal JSON POST
this.http.post(
url,
product
);
Request:
Content-Type: application/json
ASP.NET Core:
[FromBody] CreateProductDto product
File Upload
this.http.post(
url,
formData
);
Request:
Content-Type: multipart/form-data
ASP.NET Core:
[FromForm] IFormFile file
File Download
Angular:
this.http.get(
url,
{
responseType: 'blob'
}
);
ASP.NET Core:
return File(...);
So remember:
| Scenario | Angular | ASP.NET Core |
|---|---|---|
| Send JSON | Object | [FromBody] DTO |
| Upload file | FormData |
[FromForm] IFormFile |
| Download file | responseType: 'blob' |
File(...) |
Important Security Points for File Uploads
In a real application, don't simply trust:
file.FileName
file.ContentType
At minimum, validate:
- File size
- Allowed file extensions/types
- Generated server-side filename
- Storage location
- Authorization
- Malware scanning where appropriate
Instead of storing an uploaded file directly using the user-provided filename, a safer pattern is to generate your own filename:
var extension =
Path.GetExtension(file.FileName);
var storedFileName =
$"{Guid.NewGuid()}{extension}";
Then save:
3a1bd71c-8d54-4e92-a6a7-....pdf
rather than blindly trusting:
user-supplied-file-name.pdf
Also ensure download paths cannot escape the intended storage directory; Path.GetFileName() in the simple example helps strip directory components, but production systems should apply a deliberate storage/access design.
Key Points
- Use
FormDatato upload files from Angular. - Use
HttpClient.post()to send theFormData. - File uploads normally use:
multipart/form-data
- Don't manually set the multipart
Content-Typeheader; allow the browser to generate the boundary. - ASP.NET Core receives uploaded files using:
IFormFile
- Use:
[FromForm]
for multipart/form-data binding.
- Multiple files can be received as:
List<IFormFile>
- ASP.NET Core can return files using:
return File(...);
- Angular should use:
responseType: 'blob'
when downloading binary files.
- Use
URL.createObjectURL()to create a temporary browser URL for a receivedBlob. - Validate file size, type, filename, authorization and storage location on the server.
Interview Questions and Answers
1. How do you upload a file from Angular to ASP.NET Core?
Create a FormData, append the file and send it using HttpClient.post():
const formData = new FormData();
formData.append('file', file);
return this.http.post(
'/api/files/upload',
formData
);
ASP.NET Core receives it using:
[FromForm] IFormFile file
2. Why do we use FormData for file uploads?
FormData allows the browser to construct a multipart/form-data request containing binary files and ordinary form fields.
3. What is IFormFile?
IFormFile is ASP.NET Core's abstraction representing a file uploaded as part of an HTTP request.
IFormFile file
4. Should you manually set Content-Type: multipart/form-data in Angular?
Normally, no.
When sending FormData, allow the browser to set the Content-Type together with the required multipart boundary.
5. How do you upload multiple files?
Angular can append multiple files:
for (const file of files) {
formData.append('files', file);
}
ASP.NET Core can receive:
[FromForm] List<IFormFile> files
6. How does ASP.NET Core return a file?
Using the controller File() method:
return File(
bytes,
"application/pdf",
"sample.pdf"
);
7. How does Angular download a file?
Request it as a Blob:
return this.http.get(
url,
{
responseType: 'blob'
}
);
Then create an object URL and trigger the browser download.
8. Why do we use responseType: 'blob'?
Because the response contains binary file data rather than the normal JSON response.
9. What is the difference between [FromBody] and [FromForm]?
[FromBody]
↓
Typically JSON request body
↓
DTO
[FromForm]
↓
Form / multipart request
↓
Fields + IFormFile
For JSON:
[FromBody] CreateProductDto product
For file upload:
[FromForm] IFormFile file
Short Interview Answer
To upload files from Angular, create a
FormData, append the selectedFile, and send it usingHttpClient.post(). ASP.NET Core receives the multipart request using[FromForm] IFormFile. For downloads, ASP.NET Core returns the file usingFile(), while Angular requests it withresponseType: 'blob'and uses the resultingBlobto trigger the browser download.