ASP.NET Core 6: Downloading Files from the Server

In this article, we will implement the file downloading feature using ASP.NET Core 6 application. In real-world applications, we can have various files like reports, spreadsheets, images, PDF documents stored on the server. (Note that all cloud-hosted applications prefer to store these files on the storage services, e.g. Blob, S3, etc.). But what if the client prefers to store files on the separate file server mapped to the web application host server? Then, in that case, we may need to download these files from the file server. In this article, we will implement exactly the same thing.  

Step 1: Open Visual Studio 2022 and create a new ASP.NET Core MVC Application targetted to .NET 6. Name this application as Core6_FileDownload. In this project add a new folder and name it as ServerFiles. In this file add some images. (You can add Excel, PDF, Word, Files).

Step 2: Since we need to read the ServerFiles folder using the ASP.NET Core application, we need to add the IFileProvider service in the dependency container of the application. Modify the code in Program.cs as shown in listing 1

builder.Services.AddSingleton<IFileProvider>(
                new PhysicalFileProvider(
                    Path.Combine(Directory.GetCurrentDirectory(),
                    "./ServerFiles")));


Listing 1: The IFileProvider service registration

As shown in Listing 1, we are registering the IFileprovider service that configures the ServerFiles folder to read files.  

Step 2: In the Models folder, add a new class file and name it as FilesModels.cs. In this file, we will add FileModel and FilesModels classes. The FileModel class will be used to store file information like Name and Path. The FilesModels class will contains a list of FileModel. We will use the list of FileModel to show files data on the view. The code for these classes is shown in listing 2

namespace Core6_FileDownload.Models
{
    public class FileModel
    {
        public string FileName { get; set; }
        public string FilePath { get; set; }
    }

    public class FilesModels
    {
        public List<FileModel> files { get; set; } = new List<FileModel>();
    }
}


Listing 2: FileModel and FilesModels classes     

Step 3: In the Models folder, add a new class file and name it as MimeTypes.cs. In this class file, we will add the code for defining MIME Types that will be used while downloading files. The code for this class is shown in listing 3

namespace Core6_FileDownload.Models
{
    public static class MimeTypes
    {
        public static Dictionary<string, string> GetMimeTypes()
        {
            return new Dictionary<string, string>
            {
                {".png", "image/png"},
                {".jpg", "image/jpeg"},
                {".jpeg", "image/jpeg"},
                {".gif", "image/gif"}
            };
        }
    }response
}


Listing 3: MIME Types   

Step 4: In the Controllers folder, add a new empty MVC controller, name this controller as DownlodController. In this controller, we will have Index() and DownlofFile() action methods. The controller is injected with the IFileProvider.   The Index() method will read all files using the IFileProvider and add it to the list of files defined in the FilesModels class. This list is passed to View() so that files can be shown on the view. 

The DownloadFile() method accepts the file name which is requested to download. The method check for the fileName is not null or empty. The method further reads the server path for the file from the ServerFiles folder. Using the file path the file is opened using the FileStream class and copied into the MemoryStream. The DownloadFile() method further reads the MIME-Type based on the file extension and then the file is responded to from the 0the position. The code for the DownloadController is shown in Listing 4


 public class DownloadController : Controller
    {
        private readonly IFileProvider fileProvider;

        public DownloadController(IFileProvider provider)
        {
            fileProvider = provider;
        }

        public IActionResult Index()
        {
            var fileModels = new FilesModels();
            foreach (var item in fileProvider.GetDirectoryContents(""))
            {
                fileModels.files.Add(new FileModel() 
                {
                    FileName = item.Name, 
                    FilePath = item.PhysicalPath 
                });
            }
            return View(fileModels.files);
        }


        public async Task<IActionResult> DownloadFile(string fileName)
        {
            if (string.IsNullOrEmpty(fileName) || fileName == null)
            {
                return Content("File Name is Empty...");              
            }

            // get the filePath

            var filePath = Path.Combine(Directory.GetCurrentDirectory(),
                "ServerFiles", fileName);

            // create a memorystream
            var memoryStream = new MemoryStream();

            using (var stream = new FileStream(filePath, FileMode.Open))
            {
                await stream.CopyToAsync(memoryStream);            
            }
            // set the position to return the file from
            memoryStream.Position = 0;

            // Get the MIMEType for the File
            var mimeType = (string file) =>
            {
                var mimeTypes = MimeTypes.GetMimeTypes();
                var extension = Path.GetExtension(file).ToLowerInvariant();
                return mimeTypes[extension];
            };

            return File(memoryStream, mimeType(filePath), Path.GetFileName(filePath));
        }
    }

Listing 4: DownloadController 

Step 5: Scaffold an Index view from the Index() method, select the View Template as List and select the Model class FileModel. The markup code for the Index.cshtml is shown in the Listing 5


@model IEnumerable<Core6_FileDownload.Models.FileModel>

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.FileName)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.FilePath)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.FileName)
            </td>
            <td>
                 <a asp-action="DownloadFile"
               asp-route-filename="@Url.Content(item.FileName)">
               Download File
            </a>
            </td>
        </tr>
}
    </tbody>
</table>

Listing 5: Index.cshtml        

As shown in Listing 5, the table will be generated that will show file names and hyperlinks to download files. The anchor tag is bound with the DownloadFile action using the asp-action tag helper and using the asp-route-filename, the file name will be passed as a route parameter to the DownloadFile() action method. 

Modify the _Layout.cshtml file from the Shared folder to add the links for the DonloadController as shown in Listing 6


 <li class="nav-item">
     <a class="nav-link text-dark" asp-area="" 
     asp-controller="Download" asp-action="Index">Download</a>
                        </li>

Listing 6: The link for DownloadController

Run the application, from the Home age click on the Download link, this will show list of files as shown in figure 1



Figure 1: List of files  

 Click on the Download File, the file download will start as shown in the figure 2



Figure 2: File Download   

The code for the article can be downloaded from this link.

Comments

  1. The information in the post you posted here is useful because it contains some of the best information available. Thanks for sharing it. Keep up the good work free ideal pdf editor tool online.

    ReplyDelete
  2. What about that memory stream? Should we care that it's not disposed?

    ReplyDelete
  3. I am working on a project where the users need to download files that are at times very large. This solution failed in line "await stream.CopyToAsync(memoryStream)" with "IOException: Stream too long." Any idea how to solve this?

    ReplyDelete
  4. I have similar code that worked with .Net 2.1, but after I moved it to .NET 6, instead of downloading file, it is looking for a view based on the action method name, and fails. Could you please help? I posted a question with my code here: https://docs.microsoft.com/en-us/answers/questions/874470/cannot-download-files-in-net-6.html

    ReplyDelete
  5. A PSA (Public Service Announcement) is a short video informational clip that is meant to raise awareness about an important cause, and/or issue. PSAs may include videos, animations, interviews, dramatizations, and many other types of visual content. Mechanism of Action

    ReplyDelete

Post a Comment

Popular posts from this blog

ASP.NET Core 6: Adding Custom Middleware and Logging the Error Message in Database

ASP.NET Core 6: Using Role Based Security for ASP.NET Core 6 WEB API