(C# ASP.NET Core) Downloading file(s) and FileResult

A file can be downloaded by returning a FileResult. This article explains how a FileResult is created. Finally, a walkthrough is given that shows a complete upload-download scenario.

Categories | About |     |  

Parveen,

What is a FileResult?

A file download takes place when a handler returns an instance of FileResult.

A FileResult is a type of ActionResult. Hence the following are equivalent:

// return  a FileResult 
public async Task<FileResult> OnGetDownloadFileAsync(int? id)
{

  return File(.......);

}


// the above is same as 
// or ActionResult 
public async Task<IActionResult> OnGetDownloadFileAsync(int? id)
{

  return File(.......);

}

Video Explanation

Please watch the following youtube video:

How is a FileResult created? (Method 1)

If you have a byte array use this code. The function takes three arguments - first is the byte array, second is the mime type and the name of the downloaded file.

public async Task<FileResult> OnGetDownloadFileAsync(int? id)
{

  // obtain bytes of the file 
  // from database or by directly 
  // reading the files 
  byte[] fileBytes = . . . ;

  return File(
      fileBytes,         /*byte []*/
      "application/pdf", /*mime type*/
      "fileName.pdf"     /*name of the file*/
  );

}

How is a FileResult created? (Method 2)

If you have a path relative to wwwroot then use this code to directly serve the file. The function also takes three arguments - first is the relative path, second is the mime type and the name of the downloaded file.

public async Task<FileResult> OnGetDownloadFileAsync(int? id)
{

  // relative path to wwwroot 
  String fileBytes = "~/folder/myfile.pdf" ;

  return File(
      fileBytes,         /*string*/
      "application/pdf", /*mime type*/
      "fileName.pdf"     /*name of the file*/
  );

}

Step 1 of 3 - Create a Model

NOTE: it is assumed that database set up is already done

Write a model class to hold the bytes of the uploaded file.

using Microsoft.EntityFrameworkCore;

using System;

using System.ComponentModel.DataAnnotations;

using System.ComponentModel.DataAnnotations.Schema;

using System.Diagnostics.CodeAnalysis;

namespace DBaseCon
{

  public class UploadedFile
  {

    // auto-increment Primary ID key 
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    // this column is for a file BLOB 
    public byte[] FileBytes { get; set; }
  }


  // -------- DbContext ---------- // 

  // dbcontext DAL - THERE IS ONE DbContext per database 
  public class ProjectContext : DbContext
  {

    public ProjectContext(DbContextOptions options) : base(options)
    {

      // EnsureCreated is meant for testing or creating 
      // a blank database. this code should be avoided 
      // at this place. use some alternate program to 
      // create/migrate your database. this code has been 
      // used here only for tutorial purposes 
      Database.EnsureCreated();

    }

    // this function is used to specify FK relations, Indexes, 
    // and [optionally] the name of your database tables 
    // corresponding to each model 
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {

      base.OnModelCreating(modelBuilder);

      // name of the table in your database 
      modelBuilder.Entity<UploadedFile>().ToTable("product");

    }

    // MUST be PLURAL 
    public DbSet<UploadedFile> Files { get; set; }
  }

}

Step 2 of 3 - The backing class

The following code sends an IList of UploadedFile. This list is used to display a table of uploaded files with download links.

using DBaseCon;

using Microsoft.AspNetCore.Http;

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.RazorPages;

using Microsoft.EntityFrameworkCore;

using System.Collections.Generic;

using System.IO;

using System.Threading.Tasks;

namespace Modular.Pages
{

  public class IndexModel : PageModel
  {

    private readonly ProjectContext _ctx;

    // dependency injection of the ProductContext 
    public IndexModel(ProjectContext ctx)
    {

      _ctx = ctx;

    }

    // handy tool for validation error 
    [TempData]
    public string Message { get; set; }

    // onpost handler 
    public async Task<IActionResult> OnPostAsync(List<IFormFile> mylist)
    {

      foreach (IFormFile myfile in mylist)
      {

        if (
            (null == myfile)
            ||
            (myfile.Length > 1024 * 1000)
            ||
            (!"application/pdf".Equals(myfile.ContentType))
        )
        {

          Message = "File must be pdf type and less than 1 MB";

        }

        else 
        {

          var uploadedFile = new UploadedFile();

          using (MemoryStream ms = new MemoryStream())

          {

            // copy the file to memorystream 
            await myfile.CopyToAsync(ms);

            // set the byte array 
            uploadedFile.FileBytes = ms.ToArray();

          }

          _ctx.Files.Add(uploadedFile);

          await _ctx.SaveChangesAsync();

        }

      }

      return RedirectToPage();

    }


    // list of uploads 
    public IList<UploadedFile> AllUploads { get; set; }

    // onget handler get the list of 
    // already uploaded files in the database 
    public async void OnGetAsync()
    {

      AllUploads = await _ctx.Files.ToListAsync();

    }


    // download handler 
    public async Task<FileResult> OnGetDownloadFileAsync(int? id)
    {

      var uploadedFile = await _ctx.Files.FindAsync(id);

      return File (
          uploadedFile.FileBytes,
          "application/pdf",
          "fileName.pdf"
      );

    }

  }

}

Step 3 of 3 - The razor markup

Following is the markup.

@page "{handler?}"

@model Modular.Pages.IndexModel

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<h1>Upload a PDF file < 1 MB</h1>

@if (TempData.ContainsKey("Message"))
{
  <small style="color:red">

    @TempData["Message"]

  </small>
}
<form method="post" enctype="multipart/form-data">

  <input type="file"
         name="mylist"
         required
         multiple
         accept="application/pdf, image/png" />

  <p />

  <input type="submit" value="Upload!" />

</form>

@if (Model.AllUploads.Count > 0)
{
  <h2>Download files</h2>

  <table border="1">

    <tr>

      <th>
        File Size (bytes)
      </th>

      <th>
        Download
      </th>

    </tr>

    @foreach (var uploadedFile in Model.AllUploads)
    {
      <tr>

        <td>
          @uploadedFile.FileBytes.Length
        </td>

        <td>
          <a asp-page-handler="DownloadFile"
             asp-route-id="@uploadedFile.ID">download</a>
        </td>

      </tr>
    }

  </table>
}

This Blog Post/Article "(C# ASP.NET Core) Downloading file(s) and FileResult" by Parveen is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Comments and Discussion