File Upload with .NET Core and MVC - Part 4 - Upload Image and Create Thumbnail

Date Published: 5/18/2023

Check out my video courses at...

Pluralsight and at Udemy

File Upload with .NET Core and MVC - Part 4 – Upload Image and Create Thumbnail

Previously in this blog series, you learned to style the HTML file upload control, upload a file to the server, use a view model class to upload additional information with the file, and to validate the type of files that can be uploaded. In this post you learn to upload a photo and create a thumbnail version of that photo. The thumbnail version of a photo can be used when displaying a list of photos to a user. Using a smaller version of the photo keeps your web pages smaller and faster to load. You can then allow the user to click on one of the photos to display the large version.

Create Upload Folder for Files

In the previous versions of this blog post series, you stored the uploaded files in a temp path on your computer. When you upload files, you might want to provide the user with a link to display the file uploaded. In the case of a photo, you might want to display the photo on the web page after uploading. Open the appsettings.json file and add a new property to the "FileUploadSettings" JSON object to specify the folder name to upload the files to as shown in the following code snippet.

"FileUploadSettings": {
  "acceptTypes": "image/*,.pdf,.txt,.doc,.docx,.xls,.xlsx,.ppt,.pptx",
  "validFileTypes": "application/vnd.openxmlformats-officedocument,image/,application/pdf,text/plain",
  "invalidFileExtensions": ".cs,.js,.exe,.com,.bat",
  "uploadFolder" : "UploadedFiles"
}

This folder name is simply the name of the folder you create under the wwwroot folder in your project. Any files you want to reference from your web pages should be placed into a folder under the wwwroot folder. Right mouse-click on the wwwroot folder and add a new folder named UploadedFiles.

NOTE: Normally, you should not persist the uploaded files into a directory within your web application as this helps reduce the likelihood of an attack by a malicious file. However, for this blog post, you will be uploading files to a folder under your web application.

Update the Settings Class

If you remember in the previous blog posts, any values you put into the appsettings.json file, you have a corresponding property in the FileUploadSettings class to hold the value from that JSON property. Open the EntityClasses\FileUploadSettings.cs file and add the UploadFolder property (Listing 1) to match the JSON property you created in the appsettings.json file. Create another property, UploadFolderPath, which is the full physical path on the server to where the files will be stored by the ASP.NET engine.

namespace UploadVMSample.EntityClasses;

public class FileUploadSettings
{
  public FileUploadSettings()
  {
    AcceptTypes = "image/*,.pdf,.txt,.doc,.docx,.xls,.xlsx,.ppt,.pptx";
    ValidFileTypes = "application/vnd.openxmlformats-officedocument,image/,application/pdf,text/plain";
    InvalidFileExtensions = ".cs,.js,.vb";
    UploadFolder = "UploadedFiles";
    UploadFolderPath = string.Empty;
  }

  public string AcceptTypes { get; set; }
  public string ValidFileTypes { get; set; }
  public string InvalidFileExtensions { get; set; }
  public string UploadFolder { get; set; }
  public string UploadFolderPath { get; set; }
}

Listing 1: Add the UploadFolder property to the appsettings.json file.

Fill in the UploadFolderPath Property

In your web application you use .NET file IO classes to write the file uploaded to a location on the server. For file IO you need a physical path on your server, not a relative web path. As discussed, for this blog post you are uploading files into the wwwroot\UploadedFiles folder on your website. Since you are passing the FileUploadSettings object around as a singleton in the Dependency Injection container, it is a good idea to set the physical path into the UploadedFiles property before it is placed into the container.

Open the Program.cs file and locate the code where you created the instance of the FileUploadSettings class and after binding the properties from the appsettings.json file, add one additional line of code as shown in the code below to set the UploadFolderPath property. The builder.Environment object has a WebRootPath property that provides the physical path to the wwwroot folder on your website.

// Load file upload settings
FileUploadSettings settings = new();
builder.Configuration.GetSection("FileUploadSettings").Bind(settings);
settings.UploadFolderPath = builder.Environment. WebRootPath + @"\" + settings.UploadFolder;
builder.Services.AddSingleton<FileUploadSettings>(settings);

Add File Name on Server Property

Instead of using the file name uploaded by the user, you should change the file name to some random file name. This is a security best practice. Open the FileInformation.cs file and add a new property to hold the full path and file name on the server for the uploaded file.

public string FileNameOnServer { get; set; }

Also initialize this property in the constructor.

FileNameOnServer = string.Empty;

Change Location of Where to Write Files

Open the ViewModelClasses\FileSystemViewModel.cs file and change the SaveToDataStoreAsync() method to look like the code in Listing 2.

protected override async Task<bool> SaveToDataStoreAsync()
{
  bool ret = false;

  if (FileToUpload != null) {
    // Set Server Location to Store File
    FileInfo.ServerLocation = UploadSettings.UploadFolderPath + @"\";

    if (FileInfo.Type.StartsWith("image")) {
      // Create a random file name for this new file
      // Leave the file extension in place if it is an image file
      string ext = Path.GetExtension(FileInfo.FileName).ToLower();
      string fileName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName());
      FileInfo.FileNameOnServer = Path.Combine(FileInfo.ServerLocation, fileName + ext);
    }
    else {
      // Create a random file name for this new file
      FileInfo.FileNameOnServer = Path.Combine(FileInfo.ServerLocation, Path.GetRandomFileName());
    }

    // Create a stream to write the file to
    using var stream = File.Create(FileInfo.FileNameOnServer);

    // Upload file and copy to the stream
    await FileToUpload.CopyToAsync(stream);

    ret = true;
  }

  return ret;
}

Listing 2: Create a random file name on the server.

Change the FileInfo.ServerLocation property to use the UploadFolderPath property. Next, create a random file name using the Path.GetRandomFileName() and combine that with the ServerLocation to create the file name to store on the server. Using a random file name again is a security best practice for files being uploaded by users.

Try it Out

Run the MVC application and click on the Upload a File link. Fill in the Title, Description, and a file to upload. Click the Save button and you should see the file you uploaded appear in the wwwroot\UploadedFiles folder.

Store Size of Thumbnail in Configuration File

To create a thumbnail from a large image you need to decide on what size you want to make the thumbnail. Instead of hard-coding the width and height, create a percentage to which you will reduce the image. You can then get the height and width from the original image uploaded and multiply each value by the percentage value. Add a new property "thumbScale" into the appsettings.json file as shown in the following code.

"FileUploadSettings": {
  "acceptTypes": "image/*,.pdf,.txt,.doc,.docx,.xls,.xlsx,.ppt,.pptx",
  "validFileTypes": "application/vnd.openxmlformats-officedocument,image/,application/pdf,text/plain",
  "invalidFileExtensions": ".cs,.js,.exe,.com,.bat",
  "uploadFolder" : "UploadedFiles",
  "thumbScale": 0.15
}

Update the Upload Settings Class

Open the EntityClasses\FileUploadSettings.cs file and add a ThumbScale property to match the JSON property you created in the appsettings.json file. In the constructor, initialize this new property to 0.15M as shown in Listing 3.

namespace UploadVMSample.EntityClasses;

public class FileUploadSettings
{
  public FileUploadSettings()
  {
    AcceptTypes = "image/*,.pdf,.txt,.doc,.docx,.xls,.xlsx,.ppt,.pptx";
    ValidFileTypes = "application/vnd.openxmlformats-officedocument,image/,application/pdf,text/plain";
    InvalidFileExtensions = ".cs,.js,.vb";
    UploadFolder = "UploadedFiles";
    UploadFolderPath = string.Empty;
    ThumbScale = 0.15M;
  }

  public string AcceptTypes { get; set; }
  public string ValidFileTypes { get; set; }
  public string InvalidFileExtensions { get; set; }
  public string UploadFolder { get; set; }
  public string UploadFolderPath { get; set; }
  public decimal ThumbScale { get; set; }
}

Listing 3: Add three more properties to read from the appsettings.json file.

Update the FileInformation Class

In the FileInformation class you created a FileName property to hold the name of the original file uploaded. You now need to add a new property to hold the name of the thumbnail file as well. Open the FileInformation.cs file and add a new property named FileNameThumbnail.

public string FileNameThumbnail { get; set; }

Modify the constructor to initialize the new property to a string.Empty value.

FileNameThumbnail = string.Empty;

Image to Thumbnail Class

To create a thumbnail from the original photo you upload, you need a package that supports this functionality. A good one for ASP.NET Core/8 is the SixLabors.ImageSharp library that you can download from NuGet into your project. Do not try to use the System.Drawing.Common library from Microsoft as that library is only for Windows only. Open the ViewModelClasses\FileUploadViewModelBase.cs file and add a method to generate the thumbnail image as shown in Listing 4.

/// <summary>
/// Generate a thumbnail for the uploaded image
/// </summary>
public void GenerateThumbnail()
{
  // Set FileNameThumbnail property
  FileInfo.FileNameThumbnail =
    Path.GetFileNameWithoutExtension(FileInfo.FileNameOnServer)
      + "-Thumb" + Path.GetExtension(FileInfo.FileNameOnServer).ToLower();

  // Load the original image
  using Image image = Image.Load(FileInfo.FileNameOnServer);
  // Resize the image to a percentage of the size
  image.Mutate(x => x
    .Resize(
      (int)(image.Width * UploadSettings.ThumbScale),
      (int)(image.Height * UploadSettings.ThumbScale)));
  // Save the thumbnail
  image.Save(Path.Combine(FileInfo.ServerLocation, FileInfo.FileNameThumbnail));
}

Listing 4: Write code to resize the original image and create a thumbnail.

Call the GenerateThumbnail() method from the Save() method within this view model class. After you have saved the uploaded file, check to see if the file is an image type. If it is, call the GenerateThumbnail() method as shown in the code below.

if (Validate()) {
  // Save File to Data Store
  ret = await SaveToDataStoreAsync();
  if (ret) {
    // If an image type, create a thumbnail
    if (FileInfo.Type.StartsWith("image")) {
      GenerateThumbnail();
    }
  }
}

Display Both Images on the Upload Complete Page

After uploading the original image and creating the thumbnail, lets display both images on the upload complete page. Open the Views\Home\FileUploadComplete.cshtml file and modify the code to the code shown in bold in Listing 5.

@model UploadVMSample.ViewModelClasses.FileUploadViewModelBase

@{
  string fileName = System.Net.WebUtility.HtmlEncode(Model.FileInfo.FileName);
  string imgFile = "/" + Model.UploadSettings.UploadFolder + "/" + System.IO.Path.GetFileName(Model.FileInfo.FileNameOnServer);
  string imgThumbFile = "/" + Model.UploadSettings.UploadFolder + "/" + Model.FileInfo.FileNameThumbnail;
}

<div class="mt-4 row">
  <div class="col text-center">
    <h1>File Uploaded Successfully!</h1>

    <p>Title: @Model.FileInfo.Title</p>
    <p>Description: @Model.FileInfo.Description</p>
    <p>File Name: @fileName</p>
    <p>File Length: @Model.FileInfo.Length</p>
    <p>File Type: @Model.FileInfo.Type</p>
  </div>
</div>
@if (Model.FileInfo.Type.StartsWith("image")) {
  <div class="mt-4 row">
    <div class="col text-center">
      <img src="@imgFile" title="@Model.FileInfo.Title" />
    </div>
  </div>
  <div class="mt-4 row">
    <div class="col text-center">
      <img src="@imgThumbFile" title="@Model.FileInfo.Title" />
    </div>
  </div>
}

Listing 5: Display both the original image and the thumbnail

Try it Out

Run the MVC application and click on the Upload a File link. Fill in the Title, Description, and select an image or a photo to upload. Click the Save button and on the upload complete page you should see both file names and both photos like the photos shown in Figure 1.

Figure 1: Display the original photo and the thumbnail on the upload complete page.

Summary

Instead of always returning a large image back to the page to be displayed, you might consider using a thumbnail image. This helps you keep the amount of data traveling back and forth across the internet to a minimum. In this blog post you added some new properties to the appsettings.json file and created a thumbnail version of a photo uploaded to your website.


#fileupload #javascript #csharp #mvc #dotnetcore #pauldsheriff #development #programming

Check out my video courses at...

Pluralsight and at Udemy