How to download files from ASP.NET Core MVC

I have been implementing a couple of features lately that allow users to download files. During this process, I have visited various namespaces and possibilities with ASP.NET Core. In an attempt not to forget what I have learned and in the hope that this knowledge can be used by others, here is a blog post about downloading files from ASP.NET Core 😊

This post will use an ASP.NET Core MVC application as an example since that is what I am using. All of the general content about downloading files from web applications can be used by all sorts of web frameworks for .NET.

Let's start by looking into how file download work in browsers. When serving content over the web your web server reacts to a request and sends back a response. Both the request and response have headers which are basically metadata around the request and the response. A common response header is the Content-Type header that contains the media type returned in the response. Frequent media types are text/html and application/json.

When returning a response from a web server, the browser will always try to show the content in the browser window, unless instructed otherwise. To tell the browser that the response of a request shall be treated as a file for download, there's another response header named Content-Disposition to use for this exact purpose. If attaching this header to a response (with the correct information, of course) the browser will download the response as a file. Here's an example of a Content-Disposition header value:

attachment; filename="file.txt"

The value contains a range of strings separated by a semicolon. The first value tells the browser that the response contains an attachment. The second string specify the filename of the attachment. Clicking a link that triggers a response with this header value will download the response body into a file named file.txt.

If you want to dig down into the details, I recommend you to check out Mozilla's Content-Disposition documentation here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition. There are multiple details I didn't cover here like the need for both content length and content type. I won't go into more details around this here since ASP.NET Core provides the usual helper methods and classes to help hide the details around this.

Let's start by creating a new ASP.NET Core MVC application either through Visual Studio's template or on the command-line:

dotnet new mvc

Launch the project in your favorite IDE and open the HomeController.cs file. To test file downloads, create a new method named Download:

public IActionResult Download()
{
    
}

ASP.NET Core offers 4 file-related response classes that implement IActionResult:

  • FileContentResult
  • FileStreamResult
  • PhysicalFileResult
  • VirtualFileResult

You may also know about the class named FileResult which is an abstract class that all of the other classes implement. Which class to use depends on how you want to provide the content of the file and from where. FileContentResult, FileStreamResult, and VirtualFileResult handles only virtual paths within your web application deployment while PhysicalFileResult supports absolute file paths.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

While it's nice to know the various classes involved, most applications don't need to know about these details. ASP.NET Core provide two helper methods with a range over overloads. Here's a quick example of returning a file using the File method:

public IActionResult Download()
{
    byte[] bytes = Encoding.UTF8.GetBytes("My first file");
    return File(bytes, "text/plain", "file.txt");
}

The code returns the string My first file as a file named file.txt. If you want to test this, create a button somewhere in the Views\Home\Index.cshtml file:

<a href="/home/download" class="btn-primary btn">Download file</a>

When clicking the button, you should see the browser download a file named file.txt.

As already mentioned, both File and PhysicalFile provides a range of overloads. File content can be provided by byte array (as in the previous example), from a Stream, and more. I won't go into more details with all overloads here since those a simply a matter of different data types.

The last thing I quickly wanted to touch upon in this post is streaming large files. The browser will automatically download the file asynchronously. In case you need to download a large response in the browser, check out Stream large content from ASP.NET Core to avoid OutOfMemoryException. But in the case where we use the File helper method to return a large video or audio file to embed somewhere, there's a nifty little feature in ASP.NET Core called range processing. To test this, include a video file in the wwwroot folder. I downloaded the largest video from here: https://file-examples.com/index.php/sample-video-files/sample-mp4-files/.

Next, we will need to load the video into a stream. Inject IWebHostEnvironment in the controller:

private readonly IWebHostEnvironment env;

public HomeController(IWebHostEnvironment env)
{
    this.env = env;
}

And finally, load the video into a Stream and return it in the File method:

public IActionResult Video()
{
    return File(
        env
            .WebRootFileProvider
            .GetFileInfo("file_example_MP4_1920_18MG.mp4")
            .CreateReadStream(),
        "video/mp4",
        enableRangeProcessing: true);
}

Notice the last parameter to the File method named enableRangeProcessing. This will allow the browser to request the file without loading it from the beginning every time. This translates great to video widgets on a website. Add the following code to the Index.cshtml file and see the magic happen when you pull the slider beneath the video:

<video src="/home/video" width="800" controls></video>

This is the work of the enableRangeProcessing parameter. By default, this is set to false and fast forward and rewind in the video control wouldn't work as expected.