The Fairway Technologies Blog

blog

How to upload Small Files using Angular

In Angular, Technology, web development, software development, Programming No Comments

Sometimes, you may need to upload files to your server via an Angular application. There are a few different methods you may use. Today, I am going to present a method that works well for small files up to about two megabytes in size. In this blog, you build two projects: a .NET Core Web API project and an Angular project. You build these two projects from scratch using the Angular CLI, .NET Core, and Visual Studio Code editor.

The result is a page that allows you to select one or more small files using an <input type="file"> element. You then build a custom FileToUpload object that includes attributes about the file including the file contents. Finally, you send this FileToUpload object via a Web API call to a method on the server. Once the server has the file, you can choose to save it as a file on the server or however you'd like.

Figure1-3

Build .NET Core Web API

To build the .NET Core Web API application, upload the files first. Open up an instance of Visual Studio Code. Select View | Integrated Terminal from the Menu to display a terminal window at the bottom of the editor. You should see something that looks like the following:

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
XX C:\Users\YOUR_LOGIN>

I'm sure you have a directory where you create your projects somewhere on your hard drive. Navigate to that folder from within the terminal window. Enter the following commands to create a .NET Core Web API project. In my case, I'll go to the \Samples folder on my D drive.

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

Open the Web API Folder

Now that you have 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. On my computer, this is from the folder D:\Samples\FileUploadSample\FileUploadWebApi.

Figure2

Load Required Assets

Pause for a few seconds after loading the folder. You should see a new prompt appear in your Code window saying that some required assets are missing. Click on the Yes button to add these assets to the project.

Figure3

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 be running on localhost:4200 by default. This means each project will be running on a separate domain. To call the Web API methods for your Angular application, you must tell the Web API that you are allowing Cross-Origin Resource Sharing (CORS). To use CORS, you need to add a CORS package to your project. Go back to the integrated terminal and type the following command.

dotnet add package Microsoft.AspNetCore.Cors

Open the Startup.cs file and modify the ConfigureServices() method. Add the following above services.AddMvc() line.

services.AddCors();
services.AddMvc();

Next, modify the Configure() method to look like the following:

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

The code above allows you to specify a single origin from which this Web API project accepts requests. It also permits any method or header to be accepted from this origin.

NOTE : You must add the call to the UseCors() method prior to the UseMvc() method call.

Try it Out

It is a good idea to test out your Web API project and ensure it can accept requests. Press F5 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

After hitting enter, you should see a string that looks like this:

["value1","value2"]

FileToUpload Class

You might wish to convey many attributes of the file you are going to upload from the front-end to the web service. These attributes include things like the file name, the file type, the size of the file, etc. Create a C# class to accept each of these values. Create a \Models folder in the root of your Web API project. Add a new file named FileToUpload.cs. Add the following code to this new 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 FileAsText { get; set; }
  public string FileAsBase64 { get; set; }
  public byte[] FileAsByteArray { get; set; }
}

Fil Upload Controller

Next, you need a controller class to allow your Angular application to call a method to send the file to. Delete the ValuesController.cs file from the \Controllers folder. Now add a file named FileUploadController.cs back into the \Controllers folder. Add the following code into this file:

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

namespace fileUploadSampleWebApi.Controllers
{
  [Route("api/[controller]")]
  public class FileUploadController : Controller
  {
    const string FILE_PATH = @"D:\Samples\";

    [HttpPost]
    public IActionResult Post([FromBody]FileToUpload theFile)
    {
      // Create unique file name
      var filePath = FILE_PATH + DateTime.Now.ToString().Replace("/", "")
          .Replace(":", "").Replace(" ", "") + "-" + theFile.FileName;

      // Remove file type from base64 encoding, if any
      if (theFile.FileAsBase64.Contains(","))
      {
        theFile.FileAsBase64 = theFile.FileAsBase64
          .Substring(theFile.FileAsBase64.IndexOf(",") + 1);
      }

      // Convert base64 encoded string to binary
      theFile.FileAsByteArray = Convert.FromBase64String(theFile.FileAsBase64);

      // Write binary file to server path
      using (var fs = new FileStream(filePath, FileMode.CreateNew))
      {
        fs.Write(theFile.FileAsByteArray, 0, theFile.FileAsByteArray.Length);
        fs.Close();
        fs.Dispose();
      }

      return Ok();
    }
  }
}

Be sure to change the FILE_PATH constant on your machine from "D:\Samples" to a valid path that the web project has permissions to write to.

Build Angular Upload Project

It is now time to build the Angular project. From within VS Code, open the integrated terminal window. Navigate back to the folder where you created this project. Enter the following command to create a new Angular application. For my project, I'm going to go to the D:\Samples\FileUploadSample folder.

ng new FileUploadAngular

Add Web API Project to Workspace

Once the project is created, add the newly created folder named FileUploadAngular to Visual Studio Code. Select the File | Add Folder to Workspace… menu item as shown below.

Figure4

Choose the FileUploadAngular folder as shown in the following screen shot:

Figure5

You should now see two projects within VS Code as shown in the following screen shot. This is called a Workspace.

Figure6

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 can open this application by double-clicking on the FileUploadSampleApp.code-workspace file.

Create FileToUpload Class

You just created a FileToUpload class in C# to hold the various attributes about a file. Now, you want to do the same thing in Angular. Go back into the terminal window and navigate to the FileUploadAngular folder.

Figure7

Build a new class that represents a file you wish to upload.

ng g class file-upload/fileToUpload

Open the generated file-to-upload.ts file and add the following code:

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

Create File Upload Service

As with any Angular application, you should always separate any logic that communicates with a Web API into an Angular Service class. You can create a new service using the Angular CLI. 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 following import statements:

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

Add two constants just below the import statement. The first constant is the path to the FileUpload controller you created previously. 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 in order to post the file information to the Web API method. Modify the constructor of the FileUploadService class to look like the following:

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 {
  return this.http.post(
             API_URL, theFile, httpOptions);
}

File Upload Component

It is now time to build the HTML page that will prompt the user to upload a file of their choice from their local drive to the web server. Create an Angular component using the Angular CLI. This builds a .css, .html, and a .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 a user may attempt to upload. The files are going to be base64 encoded prior to sending. When you base64 encode something, it becomes larger than the original size, so it is 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 the FileUploadComponent class. The first property, theFile, holds an instance of a file object returned from the file upload object. There is no equivalent of the file object from HTML in Angular, so you must use the any data type. The second property, messages, is used to display a set of messages to the user when something happens.

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 type. You are going to respond to this change event and call a method named onFileChange() in your component. Write this code as shown below.

onFileChange(event) {
  this.theFile = null;

  // See if any file(s) have been selected from input
  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 on by the file input type. 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 is less than the size you placed into the MAX_SIZE constant. If the file meets the size requirement, 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 is in error.

Read and Upload File

Add a private method named readAndUploadFile() to the FileUploadComponent class as shown below.

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;

    // POST to server
    this.uploadService.uploadFile(file)
      .subscribe(resp => { this.messages.push("Upload complete"); });
  }
  // Read the file    
  reader.readAsDataURL(theFile);
}

Pass the theFile property to this method. You are probably thinking this is not necessary. However, when I show you later how to handle multiple file uploads, you will see why this is a good practice. Create a new instance of a FileToUpload class and set the properties from the file object retrieved from the file input type. Next, use the FileReader object from HTML 5. Note, this object only works with more modern browsers. Create a new instance of a FileReader and setup an onload() event-which is called after the file has been loaded by the readAsDataUrl() method. The readAsDataUrl() reads and returns the contents as a base64 encoded string. The file contents will be in the result property of the reader within the onload() event. 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 HTML within that file. Add the following HTML to the file:

<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">
    
    <br />
  </span>
</div>
<!-- ** END: INFORMATION MESSAGE AREA ** -->

In the <input type="file" ...>, you see the call to the onFileChange() event you wrote. The Upload File button is disabled until the theFile property is not null. When it is clicked upon, the uploadFile() method is called to start the upload process. The location where you write all messages contained within the messages array is at the end of this file.

Modify App Module

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 Code to add the appropriate import statement to this file.

imports: [
  BrowserModule,
  HttpClientModule
],

Modify App Component HTML

Open the app.component.html file and delete all the code within this file. Add the following code to display your file upload component:

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

Try it Out

If your Web API project is not still running, go ahead and start it now by pressing F5. Go to the Integrated Terminal window and start your Angular application using the following command:

npm start

Open your browser and enter localhost:4200 into the address bar. You should see a web page the looks like the following:

Figure1-3

Click on the Choose File button and select a file that is 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 a second or two, an "Upload Complete" message should be displayed. Check the folder where you specified the files be written. You should see a file name that starts with today's date followed by the file name you selected.

Upload Multiple Files

If you wish to have the user select and upload multiple files, this is also possible. Again, each file must be under one megabyte in size, but you may select as many files as you wish. To accomplish this, make the following changes in your code. 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  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 the code below:

onFileChange(event) {
  this.theFiles = [];

  // See if any file(s) have been selected from 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.");
      }
    }
  }
}

Finally, modify the uploadFile() method to loop through the list of file objects selected. Pass the current instance of the file object retrieved from the file input type to the readAndUploadFile() method each time it passes through the loop. Now you understand why it's important 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]);
  }
}

Try it Out

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

Summary

In this blog post, 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 you enable CORS to pass data from one domain to another. On the client-side, use the FileReader class to read data from the user's file system. This object is only available in more modern browsers. This technique should only be used for small files. Don't use this technique for large files as it would take too long to read the data from disk and send it to the server. And, during that downtime, you wouldn't be able to provide feedback or display what percentage of the file was 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.

Getting the Sample Code

You may download the sample code by navigating to www.fairwaytech.com/downloads . Select "PDSA/Fairway Blog" from the Category drop-down. Then select "Upload Small Files using Angular" from the Item drop-down.

New Call-to-action

Sign Up For Our Monthly Newsletter

New Call-to-action