How to Handle File Uploads with ASP.NET Core
Tags: webdev, tutorial, aspdotnet, csharp
Published: 2024-05-07 11:21:02

Managing file uploads from end users is often required in web apps and APIs. This tutorial will guide you through the process of handling uploaded files using using ASP.NET Core and C#.

> **NOTE**: To follow this tutorial, you will need the following:
> - [.NET SDK 8](https://dotnet.microsoft.com/en-us/download) installed on your machine
> - Basic knowledge of .NET, [ASP.NET Core](https://dotnet.microsoft.com/en-us/apps/aspnet) and C#
> - An IDE or text editor; we'll use [Visual Studio 2022](https://visualstudio.microsoft.com/vs/) for this tutorial, but a lightweight IDE such as [Visual Studio Code](https://code.visualstudio.com/) will work just as well

To allow files to be uploaded, you will:

1. Create ASP.NET Core [Razor](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/razor?view=aspnetcore-8.0) [views](https://learn.microsoft.com/en-us/aspnet/core/mvc/views/overview?view=aspnetcore-8.0) that allow the user to select a file to upload
2. Create ASP.NET Core [controllers](https://learn.microsoft.com/en-us/aspnet/core/mvc/overview?view=aspnetcore-8.0#controller-responsibilities) to work with uploaded files

Of course, you will also want to *do something* with each uploaded file! In this tutorial, we're going to write C# code to show information about the file, and also to scan it for malware using [Verisys Antivirus API](https://www.ionxsolutions.com/products/antivirus-api?utm_source=devto).

> _[Verisys Antivirus API](https://www.ionxsolutions.com/products/antivirus-api?utm_source=devto) is a language-agnostic REST API that allows you to easily add malware scanning to mobile apps, web apps and backend processing._
> 
> _By scanning user-generated content and file uploads, Verisys Antivirus API can stop dangerous malware at the edge, before it reaches your servers, applications - or end users._

## Project Setup
First, we need to create a new ASP.NET Core MVC project.

- Start Visual Studio and select **File > New > Project**

- Select the template named **ASP.NET Core Web App (Model-View-Controller)** and click **Next**

![Visual Studio project template selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyglhdi3tt8pagfm45o3.png)

- Enter **Web** for the **Project Name**, choose a location to save the project, then click **Next**

![Visual Studio project name](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wdlm6j19vyr3o4bmyna.png)

- For the **Framework**, select **.NET 8.0 (Long Term Support)**, then click **Create**

![Visual Studio project SDK](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/stg93yj804odjyvyv814.png)

- The new project will be created, and you should see this folder structure in Visual Studio's **Solution Explorer**:

![Folder structure visible in Solution Explorer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cqeorayai7t53mzaqdkv.png)

## Running the App
Now we've created a new project, let's make sure it runs!

- Press **CTRL+F5**, or click the **Start Without Debugging** button:

![Start without debugging](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11ylvhzs6labtso0wpsr.png)

- The first time you run the project, you will be asked to install an SSL/TLS certificate - click **Yes** on both popups:

![Installation of SSL/TLS certificate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0r7mng1fhwon6rd10cyh.png)

![Installation of SSL/TLS certificate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/glxmgubymkv5sbsgncbe.png)

- Visual Studio will then run the app, opening it in the default browser:

![ASP.NET Core web app running in a browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myzr9e63mauylr4yv8ih.png)

- While running your project, Visual Studio will open a terminal as well as the browser - this is for the default web server, Kestrel. If you press `CTRL-C`, it will shut down the web server and stop running your project.

![Start without debugging](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v713glfykd6gff3au24c.png)

## File Upload
To enable file uploads, we need to add:

1. View models, to encapsulate information about uploaded files
2. A view with a form that allows users to select and upload a file
3. A view to show information about the uploaded file
4. A controller, to show the views and handle the uploaded file
5. We'll also update the main layout view, just to add links to our new pages

Let's get started!

- First, create file `Models/FileModels.cs`, with content:

```c#
namespace Web.Models;

public record UploadModel(IFormFile File);
public record ResultModel(string Name, long Size, string MimeType, 
    MalwareStatus MalwareStatus, string[] Signals);

public record ApiResultModel(Guid Id, MalwareStatus Status, string[] Signals);
public enum MalwareStatus { Unknown, Clean, Threat }
```

- Next, create a folder `Views/Upload`, and inside a view file `Views/Upload/Index.cshtml` with content:

```html
@model UploadModel
@{
    ViewData["Title"] = "Upload File";
}

<form method="post" enctype="multipart/form-data">
    <input type="file" asp-for="File">
    <button type="submit">Upload File</button>
</form>
```

Note the form's [encoding type](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/enctype) (`enctype`) property is set to `multipart/form-data` - this is required for uploading files.

- Next, create view file `Views/Upload/Result.cshtml` with content:

```html
@model ResultModel
@{
    ViewData["Title"] = "Result";
}

<h1>Result</h1>

<div>File Name: @Model.Name</div>
<div>File Size: @Model.Size</div>
<div>Mime Type: @Model.MimeType</div>
<div>Malware Status: @Model.MalwareStatus</div>
```

- Create a controller file `Controllers/UploadController.cs` with content:

```c#
using Microsoft.AspNetCore.Mvc;
using Web.Models;

namespace Web.Controllers;

public class UploadController : Controller
{
    [HttpGet]
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public IActionResult Index(UploadModel model)
    {
        var result = new ResultModel(model.File.FileName, model.File.Length, model.File.ContentType, MalwareStatus.Unknown, []);
        return View("Result", result);
    }
}
```

- Finally, update the content of `Views/Shared/_Layout.cshtml` to add links to our new pages:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <title>@ViewData["Title"]</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
</head>
<body>
    <header>
        <nav class="navbar navbar-expand border-bottom mb-3">
            <div class="container-fluid">
                <div class="navbar-collapse">
                    <a class="nav-link text-dark" href="/upload">Upload File</a>
                    <a class="nav-link text-dark" href="/scan">Scan File</a>
                </div>
            </div>
        </nav>
    </header>

    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

</body>
</html>
```

It really is that simple!

## Testing File Uploads
Now we’re ready to test file uploads! 🎉

- Begin by running the project again, either by pressing **CTRL+F5**, or by click the **Start Without Debugging** button

- Your browser should open automatically, showing a page that looks like this:

![ASP.NET Core web app running in a browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytcg96p4h2xxy6fjuwzh.png)

- Click the link **Upload File**, and you should see one of the new views we created:

![Upload file form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ji9p2iuucsu6xvsvytls.png)

- Press **Choose File** to select a file to upload, then and press the **Upload File** button

- If everything was set up correctly, you should see information about the file being shown in a web page:

![Successful file upload](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4xyj81spdwlchfl79har.png)

Note that the **Malware Status** is shown as **Unknown** - we'll fix that in the next section.

## Malware Scanning
Now we're going to use [Verisys Antivirus API](https://www.ionxsolutions.com/products/antivirus-api?utm_source=devto) to scan uploaded files for malware. Following a similar pattern as for the previous section, we need to add:

1. Registration of an HTTP client with the Dependency Injection (DI) container
2. A view with a form that allows users to select and upload a file for scanning
3. A view to show the results of the malware scan
4. A controller, to show the views and send the uploaded file to [Verisys Antivirus API](https://www.ionxsolutions.com/products/antivirus-api?utm_source=devto)

Let's get started!

- In file `Program.cs`, replace line `builder.Services.AddControllersWithViews();` with:

```c#
// Register an HTTP client for use with Verisys Antivirus API
builder.Services.AddHttpClient<HomeController>(x =>
{
    x.BaseAddress = new Uri("https://eu1.api.av.ionxsolutions.com/v1/");
    x.DefaultRequestHeaders.Add("Accept", "application/json");
    x.DefaultRequestHeaders.Add("X-API-Key", "<YOUR API KEY HERE>");
});

builder.Services.AddControllersWithViews().AddJsonOptions(x => {
    x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    x.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});
```

> **NOTE**:
> The `X-API-Key` in the above code will need to be replaced with a real API key to scan files for real. Don’t have an API key? [Subscribe now](https://www.ionxsolutions.com/products/antivirus-api?utm_source=devto)!

- Create a folder `Views/Scan`, and inside a view file `Views/Scan/Index.cshtml` with content:

```html
@model UploadModel
@{
    ViewData["Title"] = "Scan File";
}

<form method="post" enctype="multipart/form-data">
    <input type="file" asp-for="File">
    <button type="submit">Scan File</button>
</form>
```

- Next, create view file `Views/Scan/Result.cshtml` with content:

```html
@model ResultModel
@{
    ViewData["Title"] = "Result";
}

<h1>Result</h1>

<div>File Name: @Model.Name</div>
<div>File Size: @Model.Size</div>
<div>Mime Type: @Model.MimeType</div>
<div>Malware Status: @Model.MalwareStatus</div>

@if (Model.MalwareStatus == MalwareStatus.Threat)
{
    foreach (var signal in Model.Signals)
    {
        <div>Signal: @signal</div>
    }
}
```

- Finally, create a controller file `Controllers/ScanController.cs` with content:

```c#
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Text.Json;
using Web.Models;

namespace Web.Controllers;

public class ScanController : Controller
{
    private readonly IHttpClientFactory clientFactory;
    private readonly JsonSerializerOptions jsonOptions;

    public ScanController(IHttpClientFactory clientFactory, IOptions<JsonOptions> jsonOptions)
    {
        this.clientFactory = clientFactory;
        this.jsonOptions = jsonOptions.Value.JsonSerializerOptions;
    }

    [HttpGet]
    public IActionResult Index()
    {
        return View();
    }

    [HttpPost]
    public async Task<IActionResult> Index(UploadModel model)
    {
        using var client = this.clientFactory.CreateClient(nameof(HomeController));

        using var formData = new MultipartFormDataContent();
        formData.Add(new StreamContent(model.File.OpenReadStream()), "file", model.File.FileName);
        formData.Add(new StringContent(model.File.FileName), "file_name");

        using var response = await client.PostAsync("malware/scan/file", formData);
        response.EnsureSuccessStatusCode();

        var apiModel = await response.Content.ReadFromJsonAsync<ApiResultModel>(this.jsonOptions);

        var result = new ResultModel(model.File.FileName, model.File.Length, model.File.ContentType, apiModel!.Status, apiModel.Signals);
        return View("Result", result);
    }
}
```

## Testing Malware Scanning
Now we’re ready to test malware scanning! 🎉

- Begin by running the project again, either by pressing **CTRL+F5**, or by click the **Start Without Debugging** button
   
- Your browser should open automatically, showing a page that looks like this:

![ASP.NET Core web app running in a browser](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9hlesgfjbphq3yp69mc0.png)

- Click the link **Scan File**, and you should see one of the new views we created:

![Scan file form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y5su0x07aeenuugom4ni.png)

- Press **Choose File** to select a file to upload, then and press the **Upload File** button

- If everything was set up correctly, you should see malware scan results from [Verisys Antivirus API](https://www.ionxsolutions.com/products/antivirus-api?utm_source=devto) being shown in a web page:

![Scan result of a clean, virus-free file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o97m4dlg0u77290u3bix.png)

![Scan result of an infected file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tyb08y01jdcg8gkg60hc.png)

Code for this tutorial can be found on [GitHub](https://github.com/IonxSolutions/tutorial-file-uploads-aspnetcore): https://github.com/IonxSolutions/tutorial-file-uploads-aspnetcore