Sometimes you need to upload files to your server from an Angular application to a Web API method. There are many ways to upload a file. In this article, I'm going to present a method that works well for small files, up to about one or two megabytes in size. You're going to build two projects; a .NET Core Web API project and an Angular project. You'll build these two projects from scratch using the Angular CLI, .NET Core, and the Visual Studio Code editor.

The result from this article is a page (as shown in Figure 1) that allows you to select one or more small files using an <input type="file"> element. You then build a custom FileToUpload object with attributes about the file, plus the file contents. Finally, this FileToUpload object is sent via a Web API call to a method on the server. Once the server has this file, you may choose to save it as a file on the server, into a database table, or any other location.

Figure 1: A simple file upload page
Figure 1: A simple file upload page

Build a .NET Core Web API

Let's start by building the .NET Core Web API application to which you upload files. Open an instance of Visual Studio Code. From the menu, select View > Terminal to display a terminal window at the bottom of the editor. You should see something that looks like this:

Windows PowerShell
Copyright (C) Microsoft Corporation.
All rights reserved.

XX C:\Users\YOUR_LOGIN>

I'm sure you have a directory somewhere on your hard drive in which you create your projects. Navigate to that folder from within the terminal window. For example, I'm going to go to my D drive and then to the \Samples folder on that drive. Enter the following commands to create a .NET Core Web API project.

d:
cd Samples
mkdir FileUploadSample
cd FileUploadSample
mkdir FileUploadWebApi
cd FileUploadWebApi
dotnet new webapi

Open the Web API Folder

Now that you've built the Web API project, you need to add it to Visual Studio Code. Select File > Open Folder... and open the folder where you just created the FileUploadWebApi project (Figure 2). In my case, this is from the folder D:\Samples\FileUploadSample\FileUploadWebApi. Once you've navigated to this folder, click the Select Folder button.

Figure 2: Add the Web API folder to VS Code.
Figure 2: Add the Web API folder to VS Code.

Load Required Assets

After loading the folder, VS Code may download and install some packages. After a few more seconds, you should see a new prompt appear (Figure 3) in VS Code saying that some required assets are missing. Click on the Yes button to add these assets to this project.

Figure 3: Answer Yes to the prompt to add missing assets.
Figure 3: Answer Yes to the prompt to add missing assets.

Enable Cors

The Web API project is going to run on the address localhost:5000 by default. However, when you create a new Angular application, it will run on localhost:4200 by default. This means that each Web application is running on a separate domain from each other. For your Angular application to call the Web API methods, you must tell the Web API that you're allowing Cross-Origin Resource Sharing (CORS). To use CORS, add the Microsoft.AspNetCore.Cors package to your project. Go back to the terminal window and type the following command:

dotnet add package Microsoft.AspNetCore.Cors

After the package is installed, inform ASP.NET that you're using the services of Cors. Open the Startup.cs file and locate the ConfigureServices() method. Call the AddCors() method on the services object as shown in the following code snippet:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors();
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Scroll down within the Startup.cs file and locate the Configure() method. Listing 1 shows you the code to add to allow the Web API to accept requests only from localhost:4200. Allow all HTTP methods such as GET, PUT, etc., by calling the method named AllowAnyMethod(). Allow any HTTP headers within the Web API call by invoking the method AllowAnyHeader(). Call the app.UseCors() method prior to calling the app.UseMvc() method.

Listing 1: Call UseCors() from the Configure() method in the Startup.cs file.

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }
    
    app.UseCors(options => options.WithOrigins("http://localhost:4200").AllowAnyMethod().AllowAnyHeader());
    
    app.UseHttpsRedirection();
    app.UseMvc();
}

Try It Out

It's a good idea to test out your Web API project and ensure that it accepts requests as expected. Select Debug > Start Debugging from the Code menu to build the .NET Core Web API project and launch a browser. The browser will come up with a blank page. Type the following into the browser address bar:

http://localhost:5000/api/values

Press the Enter key to submit the request and you should see a string that looks like the following:

["value1","value2"]

Add a FileToUpload Class

A file you upload from your local hard drive has attributes that you should transmit to the Web service. These attributes are things like the file name, the file type, the size of the file, etc. You're going to also add some additional properties to hold the file contents as a Base64 encoded string and a byte array. Create a \Models folder in the root of your Web API project. Add a new file named FileToUpload.cs into this folder. Add the code shown in the following code snippet to this file:

using System;

public class FileToUpload
{
    public string FileName { get; set; }
    public string FileSize  { get; set; }
    public string FileType  { get; set; }
    public long LastModifiedTime  { get; set; }
    public DateTime LastModifiedDate { get; set; }
    public string FileAsBase64 { get; set; }
    public byte[] FileAsByteArray { get; set; }
}

Add a FileUpload Controller

Obviously, you need a controller class to which you send the file. Delete the ValuesController.cs file from the \Controllers folder. Add a file named FileUploadController.cs into the \Controllers folder. Add the code shown below to this file:

using System;
using System.IO;
using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
public class FileUploadController : Controller
{
    const string FILE_PATH = @"D:\Samples\";
    
    [HttpPost]
    public IActionResult Post([FromBody]FileToUpload theFile) 
    {
        return Ok();
    }
}

The above code creates the route for the FileUploadController, sets up a constant with the path to a folder on your local hard drive that your Web project has permissions to write to, and sets up the Post() method to which you send an object of the type FileToUpload.

Instead of the hard-coded constant for the file path to write the uploaded file to, you should take advantage of the ASP.NET Core configuration system to store the path. For this article, I wanted to keep the code simple, and used a constant.

Create a Unique File Name

Start creating the code within the Post() method now. Just above the return Ok(); line, define a new variable named filePathName. Create the full path and file name to store the uploaded file into. Use the FILE_PATH constant, followed by the FileName property, without the file extension, from the file object uploaded. To provide some uniqueness to the file name, add on the current date and time. Remove any characters that aren't valid for a file by using the Replace() method. Finish the file name by adding on the file extension from the file object uploaded.

var filePathName = FILE_PATH + Path.GetFileNameWithoutExtension(theFile.FileName) + "-" +
    DateTime.Now.ToString().Replace("/", "").Replace(":", "").Replace(" ", "") +
    Path.GetExtension(theFile.FileName);

If you're going to have multiple users uploading files at the same time, you might also want to add a session ID, a GUID, or the user's name to the file name so you won't get any name collisions.

Remove File Type

In the Angular code you're going to write, you're going to be reading the file from the file system using the FileReader class. This class is only available on newer browsers. The FileReader class reads the file from disk as a Base64-encoded string. At the beginning of this Base64-encoded string is the type of file read from the disk followed by a comma. The rest of the file contents are after the comma. Next is a sample of what the contents of the file uploaded look like.

"data:image/jpeg;base64,/9j/4AAQSkZJRgABAg..."

Write the following code to strip off the file type. Check to ensure that the file type is there so you don't get an error by passing a negative number to the Substring() method.

if (theFile.FileAsBase64.Contains(","))
{
    theFile.FileAsBase64 = theFile.FileAsBase64.Substring(theFile.FileAsBase64.IndexOf(",") + 1);
}

Convert to Binary

Don't store the file uploaded as a Base64-encoded string. You want the file to be useable on the server just like it was on the user's hard drive. Convert the file data into a byte array using the FromBase64String() method on the .NET Convert class. Store the results of calling this method in to the FileAsByteArray property on the FileToUpload object.

theFile.FileAsByteArray = Convert.FromBase64String(theFile.FileAsBase64);

Write to a File

You're finally ready to write the file to disk on your server. Create a new FileStream object and pass the byte array to the Write() method of this method. Pass in a zero as the second parameter and the length of the byte array as the third parameter so the complete file is written to disk.

using (var fs = new FileStream(filePathName, FileMode.CreateNew)) 
{
    fs.Write(theFile.FileAsByteArray, 0, theFile.FileAsByteArray.Length);
}

I've purposefully left out any error handling, but you should add some into this method. I'll leave that as an exercise for you to do. Let's move on and create an Angular project to upload files to this Web API.

Build an Angular Upload Project

Build an Angular project from within VS Code by opening the terminal window. Navigate to the folder you created at the beginning of this article. For example, I navigate to the folder D:\Samples\FileUploadSample. Enter the following Angular CLI command in the terminal window to create a new Angular application:

ng new FileUploadAngular

Add Web API Project to Workspace

Once the project is created, add the newly created folder named FileUploadAngular to VS Code. Select the File > Add Folder to Workspace... menu item, as shown in Figure 4.

Figure 4: Add the Web API project to VS Code.
Figure 4: Add the Web API project to VS Code.

Choose the FileUploadAngular folder as shown in Figure 5 and click the Add button.

Figure 5: Add the folder where the Angular project is located to VS Code.
Figure 5: Add the folder where the Angular project is located to VS Code.

You should now see two projects within VS Code, as shown in Figure 6.

Figure 6: Add the Web API folder to VS Code
Figure 6: Add the Web API folder to VS Code

Save the Workspace

Click File > Save Workspace As... and give it the name FileUploadSampleApp. Click the Save button to store this new workspace file on disk. From now on, you may always open this application by double-clicking on the FileUploadSampleApp.code-workspace file.

Create FileToUpload Class

Just as you created a FileToUpload class in C# to hold the various attributes about a file, create the same class in Angular. Go back into the terminal window and navigate to the FileUploadAngular folder, as shown in Figure 7.

Figure 7: Add the Web API folder to VS Code.
Figure 7: Add the Web API folder to VS Code.

Use the Angular CLI command ng g cl to create a new class that represents a file to upload. You can create a folder named file-upload at the same time you create the FileToUpload class. The FileToUpload class will be created in this new folder.

ng g cl file-upload/fileToUpload

Open the newly generated file-to-upload.ts file and add the code shown next. Notice that this class has the same property names as the C# class you created earlier. Using the same property names allows you to post this object to the Web API and the data contained in each matching property name is automatically mapped to the properties in the C# class.

export class FileToUpload {
    fileName: string = "";
    fileSize: number = 0;
    fileType: string = "";
    lastModifiedTime: number = 0;
    lastModifiedDate: Date = null;
    fileAsBase64: string = "";
}

Create File Upload Service

As with any Angular application, separate the logic that communicates with a Web API into an Angular service class. You can create a new service using the Angular CLI command ng g s. In the Integrated Terminal window, enter the following command to create a FileUploadService class:

ng g s file-upload/fileUpload -m app.module

Open the newly generated file-upload.service.ts file and add the import statements in the next snippet. The HttpClient and HttpHeaders classes that are imported are used to send an HTTP request. The Observable class from the RxJS library is commonly used as a return value from service calls. The FileToUpload class is needed so you can pass the data about the file to upload to the Web API.

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { FileToUpload } from './file-to-upload';

Add two constants just below the import statements. The first constant is the path to the FileUpload controller that you created earlier in this article. The second constant is a header used to post JSON data to the Web API.

const API_URL = "http://localhost:5000/api/FileUpload/";
const httpOptions = {
    headers: new HttpHeaders({
        'Content-Type': 'application/json'
    })
};

The HttpClient class needs to be injected into this service class to post the file information to the Web API method. Modify the constructor of the FileUploadService class to tell the Dependency Injection system of Angular to pass in an instance of the HttpClient class.

constructor(private http: HttpClient) { }

Add a method named uploadFile() to which you pass an instance of the FileToUpload class. This class is built using properties from a File object retrieved from the user via an <input type="file"> element. Call the post() method on the HttpClient class passing in the URL where the controller is located, the file object to post and the HTTP header constant.

uploadFile(theFile: FileToUpload) : Observable<any> {
    return this.http.post<FileToUpload>(API_URL, theFile, httpOptions);
}

File Upload Component

It's now time to build the HTML page that prompts the user for a file from their local drive that they wish to upload to the Web server. Create an Angular component using the Angular CLI command ng g c. This command builds a .css, .html, and .ts file from which you build the file upload page. In the terminal window, enter the following command:

ng g c fileUpload

Open the file-upload.component.ts file and add two import statements for the FileUploadService and the FileToUpload class.

import { FileUploadService } from './file-upload.service';
import { FileToUpload } from './file-to-upload';

You should limit the maximum size of file that a user may attempt to upload. The files are going to be Base64 encoded prior to sending. When you Base64-encode something, it grows larger than the original size. So it's best to limit the size of the file to upload. Add a constant just below the import statements and set a limit of one megabyte. Feel free to play with different file sizes to see what works with your situation.

// Maximum file size allowed to be uploaded = 1MB
const MAX_SIZE: number = 1048576;

You need to add two public properties to FileUploadComponent class. The first property, theFile, holds an instance of a file object returned from the file upload object. There's no equivalent of the HTML file object in Angular, so you must use the any data type. The second property, messages, is used to display one or more messages to the user.

theFile: any = null;
messages: string[] = [];

The FileUploadComponent class creates an instance of a FileToUpload class from the information contained in the theFile property. It then passes this object to the FileUploadService to upload the file to the server. Inject the FileUploadService into the constructor of the FileUploadComponent.

constructor(private uploadService: FileUploadService) { }

Get File Information from Change Event

When the user selects a file from their file system, the change event is fired on the file input element. You're going to respond to this change event and call a method named onFileChange() in your component. Write this code as shown here:

onFileChange(event) {
    this.theFile = null;
    if (event.target.files && event.target.files.length > 0) {
        // Don't allow file sizes over 1MB
        if (event.target.files[0].size < MAX_SIZE) {
            // Set theFile property
            this.theFile = event.target.files[0];
        }
        else {
            // Display error message
            this.messages.push("File: " + event.target.files[0].name + " is too large to upload.");
        }
    }
}

In the onFileChange() method an argument, named event, is passed in by the file input element. You check the target.files property of this argument to see if the user selected a file. If a file is selected, check the size property to make sure it's less than the size you placed into the MAX_SIZE constant. If the file meets the size requirement, you retrieve the first element in the files array and assign it to the theFile property. If the file exceeds the size requirement, push a message onto the messages array to inform the user of the file name that's in error.

Read and Upload File

Add a private method named readAndUploadFile() to the FileUploadComponent class as shown in Listing 2. Pass the theFile property to this method. Although you could simply access theFile property from this method, later in this article, you're going to need to pass a file object to this method to support multiple file uploads. Create a new instance of a FileToUpload class and set the properties from the file object retrieved from the file input element.

Listing 2: Read a file using the FileReader class

private readAndUploadFile(theFile: any) {
    let file = new FileToUpload();
    
    // Set File Information
    file.fileName = theFile.name;
    file.fileSize = theFile.size;
    file.fileType = theFile.type;
    file.lastModifiedTime = theFile.lastModified;
    file.lastModifiedDate = theFile.lastModifiedDate;
    
    // Use FileReader() object to get file to upload
    // NOTE: FileReader only works with newer browsers
    let reader = new FileReader();
    
    // Setup onload event for reader
    reader.onload = () => {
        // Store base64 encoded representation of file
        file.fileAsBase64 = reader.result.toString();
        
        // POST to server
        this.uploadService.uploadFile(file).subscribe(resp => { 
            this.messages.push("Upload complete"); });
    }
    
    // Read the file
    reader.readAsDataURL(theFile);
}

Use the FileReader object from HTML 5 to read the file from disk into memory. Note that this FileReader object only works with modern browsers. Create a new instance of a FileReader class, and setup an onload() event that's called after the file has been loaded by the readAsDataUrl() method.

The readAsDataUrl() reads the contents and returns the contents as a Base64 encoded string. Within the onload() event, you get the file contents in the result property of the reader. Place the contents into the fileAsBase64 property of the FileToUpload object. Call the uploadFile() method on the FileUploadService class, passing in this FileToUpload object. Upon successfully uploading the file, push the message “Upload Complete” onto the messages array to have it displayed to the user. The readAndUploadFile() method is called in response to the Upload File button's click event.

uploadFile(): void {
    this.readAndUploadFile(this.theFile);
}

The File Upload HTML

Open the file-upload.component.html file and delete all of the HTML within that file. Add the HTML shown in Listing 3 to create the page shown back in Figure 1. In the <input type="file" ...> element, you see that the change event is mapped to the onFileChange() method you wrote earlier. The Upload File button is disabled until the theFile property is not null. When this button is clicked on, the uploadFile() method is called to start the upload process.

Listing 3: Use an input type of file to allow the user to select a file to upload

<h1>File Upload</h1>

<label>Select a File (&lt; 1MB)</label><br/>
<input type="file" (change)="onFileChange($event)" /><br/>
<button (click)="uploadFile()" [disabled]="!theFile">Upload File</button><br/><br/>

<!-- ** BEGIN: INFORMATION MESSAGE AREA ** -->
<div *ngIf="messages.length > 0">
    <span *ngFor="let msg of messages">
        {{msg}}
        <br />
        </span>
    </div>
    <!-- ** END: INFORMATION MESSAGE AREA ** -->

After the input and button elements, you see another <div> element that's only displayed when there are messages within the messages array. Each message is displayed within a <span> element. These messages can display error or success messages to the user.

Modify App Module

Because you're using the HttpClient class in your FileUploadService class, you need to inform Angular of your intention to use this class. Open the app.module.ts file and add a reference to the HttpClientModule to the imports property. After typing the comma, and HttpClientModule, hit the Tab key, or use the light bulb in VS Code, to add the appropriate import statement to the app.module.ts file.

imports: [BrowserModule, HttpClientModule],

Modify App Component HTML

The app.component.html file is the first file displayed from the index.html page. Angular generates some default HTML into this file. Delete all the code within this file and add the code shown below to display your file upload component.

<app-file-upload></app-file-upload>

Try it Out

Start the Web API project, if it's not already running, by selecting Debug > Start Debugging from the VS Code menu. Open a terminal window in the FileUploadAngular folder and start your Angular application using the Angular CLI command npm start, as shown in the next snippet:

npm start

Open your browser and enter localhost:4200 into the address bar. You should see a Web page that looks like Figure 1. Click on the Choose File button and select a file that's less than one megabyte in size. After you select a file, the name of that file appears to the right of this button, and the Upload File button becomes enabled. Click the Upload File button and after just a second or two, an “Upload Complete” message should be displayed. Check the folder where you specified to write the files and you should see the file name you selected followed by today's date. Open the file and ensure that all the file contents were written correctly.

Upload Multiple Files

The HTML file input type can upload multiple files by adding the multiple attribute to the input element. Each file to upload must be under one megabyte in size, but you may select as many files as you wish. Open the file-upload.component.html file and modify the HTML shown in bold below:

<label>Select a File(s) (&lt; 1MB)</label><br/>
<input type="file" (change)="onFileChange($event)" multiple="multiple" /><br/>
<button (click)="uploadFile()" [disabled]="!theFiles.length">Upload {{theFiles.length}} File(s)</button>

Open the file-upload.component.ts file and locate the following line of code:

theFile: any = null;

Modify this from a single object to an array of file objects.

theFiles: any[] = [];

Modify the onFileChange() method so it looks like Listing 4. Finally, modify the uploadFile() method to loop through the list of file objects selected. Each time through the loop, pass the current instance of the file object retrieved from the file input type to the readAndUploadFile() method. You now understand why it was important for you to pass a file object to the readAndUploadFile() method.

uploadFile(): void {
    for (let index = 0; index < this.theFiles.length; index++) {
        this.readAndUploadFile(this.theFiles[index]);
    }
}

Listing 4: Modify the onFileChange() method to support multiple files

onFileChange(event) {
    this.theFiles = [];
    
    // Any file(s) selected from the input?
    if (event.target.files && event.target.files.length > 0) {
        for (let index = 0; index < event.target.files.length; index++) {
            let file = event.target.files[index];
            // Don't allow file sizes over 1MB
            if (file.size < MAX_SIZE) {
                // Add file to list of files
                this.theFiles.push(file);
            }
            else {
                this.messages.push("File: " + file.name + " is too large to upload.");
            }
        }
    }
}

Try it Out

Save all your changes. Go back to the browser and you may now select multiple files. Select a few files, click the Upload Files button and ensure that all files are uploaded to the appropriate folder.

Summary

In this article, you learned to upload small files from your client Web application to a server using Angular and a .NET Core Web API project. On the server-side, you need to make sure that you enable CORS to pass data from one domain to another. On the client-side, you're using the FileReader class to read data from the user's file system. The technique illustrated in this article should only be used for small files, as it would take too long to read a large file from disk, and too long to send to the server. During this time, you can't provide feedback to the user and you won't be able to display a percentage uploaded. There are several good open-source libraries for uploading large files to the server that do provide feedback as the upload process is happening.