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.
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.