Being able to upload files and use them afterward is the required feature of many applications. Sometimes this is not a trivial task to accomplish.

So, this is going to be the topic for this blog post.

We are going to upload files to the server (.NET Core Web API part) and then use those files in our Angular client app. Also, in our next article, we are going to show how to download files using ASP.NET Core WebAPI and Angular and with it, we are going to complete this story.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

In this post, we will stick to the images, but the logic is reusable for other file types as well.


VIDEO: Uploading Files with ASP.NET Core WEB API and Angular video.


We have created the starter project to work with through this blog post and it can be downloaded from Upload Files .NET Core Angular Starter Project. We strongly recommend downloading this project because it would be much easier for you to follow along. In this project, we’ll create a new user and display all the created users as an additional feature. We are going to modify the create-logic part by adding an upload functionality having a new user created together with an image path related to them.

If you want to download our finished project, you can do that from Upload Files .NET Core Angular Finished Project.

Let’s start.

Controller and Action Logic – .NET Core Part

After we’ve downloaded our starter project, we can start by opening the UploadFilesServer project.

This project is created on top of the SQL database, so to create that database, we need to run the update-database command in a Package Manager Console window. By doing this, we will execute our migrations and create the database with the required table.

The next step is to create a newResources folder and inside it a new folder Images:

static files - how to upload files with .Net core

To continue, let’s create a simple API Controller file in the Controllers folder and name it UploadController.

Let’s modify that file by adding a new action that will be responsible for the upload logic:

[HttpPost, DisableRequestSizeLimit]
public IActionResult Upload()
{
    try
    {
        var file = Request.Form.Files[0];
        var folderName = Path.Combine("Resources", "Images");
        var pathToSave = Path.Combine(Directory.GetCurrentDirectory(), folderName);
        if (file.Length > 0)
        {
            var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
            var fullPath = Path.Combine(pathToSave, fileName);
            var dbPath = Path.Combine(folderName, fileName);
            using (var stream = new FileStream(fullPath, FileMode.Create))
            {
                file.CopyTo(stream);
            }
            return Ok(new { dbPath });
        }
        else
        {
            return BadRequest();
        }
    }
    catch (Exception ex)
    {
        return StatusCode(500, $"Internal server error: {ex}");
    }
}

 

We use a POST action for the upload-related logic and disable the request size limit as well.

The logic inside this action is pretty straightforward. We extract the file from the request and provide the path to store the file. Moreover, if the file has a length greater than zero, we just take its name and provide a full path on the server to store our file and a path to the database. This database path is going to be returned as a result of this action after we place our stream into the defined folder. We could also check if a file with the same name already exists, but didn’t want to make the code more complicated at this moment.

To avoid the MultiPartBodyLength error, we are going to modify our configuration in the Program.cs class:

builder.Services.Configure<FormOptions>(o =>
{
    o.ValueLengthLimit = int.MaxValue;
    o.MultipartBodyLengthLimit = int.MaxValue;
    o.MemoryBufferThreshold = int.MaxValue;
});

Improve Reading from a Form Body

In our previous example, we use the Request.Form to read a form body and for smaller applications, this is just fine. But here, we are using a synchronous way to read the content from the form body, which in larger applications with so many users can lead to thread pool starvation. To prevent that, we can use asynchronous reading with the Request.ReadFormAsync() expression.

All we have to do is modify the action signature and modify our code inside just a bit:

[HttpPost, DisableRequestSizeLimit]
public async Task<IActionResult> Upload()
{
    try
    {
        var formCollection = await Request.ReadFormAsync();
        var file = formCollection.Files.First();
    
        //everything else is the same

This way, we read the form body in an asynchronous way and prevent the thread pool starvation.

Serving Static Files

Usually, all the files in the wwwroot folder are servable for the client applications. We provide that by adding app.UseStaticFiles() in the Startup class in the Configure method. Of course, our uploaded images will be stored in the Resources folder, and due to that, we need to make it servable as well. To do that, let’s modify the Program.cs class:

app.UseHttpsRedirection();
app.UseCors("CorsPolicy");

app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions()
{
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), @"Resources")),
    RequestPath = new PathString("/Resources")
});

And that’s all it takes. We have prepared our server-side app and it is time to jump right to the client-side code.

If you want to learn in great detail about .NET Core project development, you can visit the .NET Core Tutorial.

Upload Files – Angular Part

Let’s open the UploadFilesClient project and take a look at the app component files. For the sake of simplicity, we have implemented all of our logic inside the app component.

To learn in great detail about Angular project development, you can read the Angular Tutorial.

So, the first thing we are going to do is to create a new Upload component in which we will handle all the upload-related logic:

ng g component upload --skip-tests

This will create three files in the upload folder, and we are going to modify the upload.component.ts file first:

import { HttpClient, HttpEventType, HttpErrorResponse } from '@angular/common/http';
import { Component, EventEmitter, OnInit, Output } from '@angular/core';

@Component({
  selector: 'app-upload',
  templateUrl: './upload.component.html',
  styleUrls: ['./upload.component.css']
})
export class UploadComponent implements OnInit {
  progress: number;
  message: string;
  @Output() public onUploadFinished = new EventEmitter();
  
  constructor(private http: HttpClient) { }

  ngOnInit() {
  }

  uploadFile = (files) => {
    if (files.length === 0) {
      return;
    }

    let fileToUpload = <File>files[0];
    const formData = new FormData();
    formData.append('file', fileToUpload, fileToUpload.name);
    
    this.http.post('https://localhost:5001/api/upload', formData, {reportProgress: true, observe: 'events'})
      .subscribe({
        next: (event) => {
        if (event.type === HttpEventType.UploadProgress)
          this.progress = Math.round(100 * event.loaded / event.total);
        else if (event.type === HttpEventType.Response) {
          this.message = 'Upload success.';
          this.onUploadFinished.emit(event.body);
        }
      },
      error: (err: HttpErrorResponse) => console.log(err)
    });
  }
}

So, what’s going on here?

We create two public properties. The first one is to hold the message when the upload action is finished and the second one is to show the upload progress. In the uploadFile function, we create a formData object and append the file that we want to upload.

The next action is to send a post request and pay attention to it. Besides the URL and body properties, we have another JSON object which states that we want to track changes of our HTTP request progress. As long as the upload is in progress, we will update the progress variable and show that percentage on the screen, but as soon as the upload is finished, we are going to write a message on the screen and emit a new event.

This event contains the body of our response, which is simply the database path of our uploaded file. We need that path to display the uploaded image with other user details.

The files with the small size will be instantly uploaded so, we will see 100% progress as soon as we select our file. But for the larger files, the progress bar will update its values for sure.

Template File Modification

To display all of the mentioned functionalities on the screen, we need to modify the upload.component.html file now:

<div class="row" style="margin-bottom:15px;">
  <div class="col-md-3">
    <input type="file" #file placeholder="Choose file" (change)="uploadFile(file.files)" style="display:none;">
    <button type="button" class="btn btn-success" (click)="file.click()">Upload File</button>
  </div>
  <div class="col-md-4">
    <span class="upload" *ngIf="progress > 0">
      {{progress}}%
    </span>
    <span class="upload" *ngIf="message">
      {{message}}
    </span>
  </div>
</div>

This logic is pretty straightforward except for the part where we hide the actual upload control and use its reference (#file) to invoke its click event with the button, which looks much better. We could have styled the upload control as well, but this is the better way, at least from our point of view.

Finally, let’s modify the upload.component.css file:

.upload{
  font-weight:bold;
  color:#28a745;
  margin-left: 15px;
  line-height: 36px;
}

And add a selector from the upload component to the app.component.html file:

<app-upload></app-upload>
<div class="row">
  <div class="offset-md-5 col-md-2">
    <button type="button" class="btn btn-primary" (click)="onCreate()">Create </button>
  </div>
</div>

Excellent. We can now inspect our result:

create user - How to upload files with .Net Core

We can check our Resources/Images folder as well, to be sure that the files are really uploaded:

file uploaded - How to upload files in .NET Core

Using Uploaded Files in Our Application

As soon as we press the Create button on our form, we are going to see our newly created user. But its profile picture won’t be rendered. So, let’s fix that.

First, we need to react to the onUploadFinished event from the update component, and to do that let’s modify the app.component.html file:

<app-upload (onUploadFinished)="uploadFinished($event)"></app-upload>

This change forces us to modify the app.component.ts file as well.

First, let’s add an additional property to that file:

response: {dbPath: ''};

Then let’s add the uploadFinished function to populate this property:

uploadFinished = (event) => { 
  this.response = event; 
}

With this modification, we have the response object in which we can find a path to be saved in the database.

Lastly, we have to modify the user object in the onCreate function in the same file:

onCreate = () => {
  this.user = {
    name: this.name,
    address: this.address,
    imgPath: this.response.dbPath
  }

Now we know the image file path related to the created user, so let’s use that knowledge to render that picture next to other user details.

To do that, let’s change the table inside the app.component.html file:

<table class="table table-striped">
  <thead>
    <tr>
      <th scope="col">Image</th>
      <th scope="col">Name</th>
      <th scope="col">Address</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let user of users">
      <td><img [src]="createImgPath(user.imgPath)" alt="profile picture" style="width:60px; height:60px;"></td>
      <td>{{user.name}}</td>
      <td>{{user.address}}</td>
    </tr>
  </tbody>
</table>

And let’s modify the app.component.ts file by adding the createImgPath function:

public createImgPath = (serverPath: string) => { 
  return `https://localhost:5001/${serverPath}`; 
}

Now, once we upload the file and create a user, we can see all the info in the table:

used downloaded picture

Before we move to the multiple files upload functionality, just a reminder that if you want to learn how to upload files using ASP.NET Core Web API and Blazor WebAssembly, you can read the BlazorWebassembly File Upload article from the Blazor WASM series.

Uploading Multiple Files

If we want to upload multiple files in any of our projects, we need to modify both the server and client-side code.

So let’s start with the server-side:

public IActionResult Upload()
{
    try
    {
        var files = Request.Form.Files;
        var folderName = Path.Combine("StaticFiles", "Images");
        var pathToSave = Path.Combine(Directory.GetCurrentDirectory(), folderName);

        if (files.Any(f => f.Length == 0))
        {
            return BadRequest();
        }

        foreach (var file in files)
        {
            var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"');
            var fullPath = Path.Combine(pathToSave, fileName);
            var dbPath = Path.Combine(folderName, fileName); //you can add this path to a list and then return all dbPaths to the client if require

            using (var stream = new FileStream(fullPath, FileMode.Create))
            {
                file.CopyTo(stream);
            }
        }

        return Ok("All the files are successfully uploaded.");
    }
    catch (Exception ex)
    {
        return StatusCode(500, "Internal server error");
    }
}

After this modification, let’s change the client-side. First, we need to modify the input type file control by adding the multiple attribute:

<input type="file" #file placeholder="Choose file" 
(change)="uploadFile(file.files)" style="display:none;" multiple>

After that, we are going to modify the uploadFile function:

uploadFile = (files) => {
  if (files.length === 0) {
    return;
  }

  let filesToUpload : File[] = files;
  const formData = new FormData();
  
  Array.from(filesToUpload).map((file, index) => {
    return formData.append('file'+index, file, file.name);
  });

  this.http.post('https://localhost:5001/api/upload', formData, {reportProgress: true, observe: 'events'})
    .subscribe(
      {next: (event) => {
      if (event.type === HttpEventType.UploadProgress)
        this.progress = Math.round(100 * event.loaded / event.total);
      else if (event.type === HttpEventType.Response) {
        this.message = 'Upload success.';
        this.onUploadFinished.emit(event.body);
      }
    },
    error: (err: HttpErrorResponse) => console.log(err)
  });
}

One interesting thing to pay attention to is the use of the Array.from() function. Even though the filesvariable contains all the selected files, it is not an array. So, in order to use the mapfunction, we are using the Array.from()syntax, which will convert the array-like object into the new array copy.  The rest of the logic is pretty straight forward.

And that is all that takes. Now you can test your code and check that your files are uploaded.

Conclusion

In this article, we have learned:

  • How to code our server-side action to handle file uploading
  • The way to create an upload component in our Angular application
  • How to use uploaded files in the Angular application

In the next article, you can read how to Download Files with ASP.NET Core Web API and Angular.

 

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!