Kriya and Meditation for Software / IT Professionals. Conducted by Bipin Joshi in Thane. Read more...
Learn ASP.NET MVC, ASP.NET Core and ASP.NET Design Patterns. Courses conducted by Bipin Joshi on weekends. Click here for more details.

Track Number of File Downloads in ASP.NET MVC

A common requirement in content management systems, forums and portals is to track the number of times a file has been downloaded. Such a tracking gives you an idea as to how many users downloaded a file and thus can also hint at the popularity / usefulness of the content. One can always put a hyperlink directly pointing to the file that is to be downloaded. However, such direct links don't reveal any information about the number of times a file was downloaded. Luckily, with a little bit of coding you can track file downloads in ASP.NET MVC. Let's see how.

Database table for storing file details

In order to track file downloads firstly you should avoid direct links to the file under consideration. You can generate hyperlinks that point to an action method which in turn returns the file. This indirect way of returning files allows you to track the number of downloads (and also users who downloaded it, in case membership is enabled). Since hyperlinks are no longer pointing to files themselves, you need a database table that stores a list of file IDs and their actual path. Consider the following database table that shows how this can be done:

The Files table has five columns - FileID, FileDisplayName, FilePath, FileType and DownloadCount. The FileID is an identity column. The FileDisplayName column contains a friendly name of a file that is displayed to the user. The FilePath column contains the ~ qualified virtual path of the file. This path is never shown to the end user. It is used by the action method internally (I will discuss this shortly). The FileType column holds the MIME content type for a file. This value is used while downloading the file. Finally, the DownloadCount column stores the number of times a file was downloaded.

Displaying a list of files

Now, let's display a list of files that can be downloaded to the end user. To do this you need to fetch data from the Files table discussed earlier and supply it as a model to the Index view. The Index() action looks like this:

public ActionResult Index()
{
    using (FilesDbEntities db = new FilesDbEntities())
    {
        return View(db.Files.ToList());
    }
}

The Index view then iterates through the List of files and displays them in a table:

@model List<CSVDownloadDemo.Models.File>
...
<h1>List of Files</h1>
<table border="1" cellpadding="10">
  <tr>
    <th>File ID</th>
    <th>File Name</th>
    <th>File Type</th>
    <th>Downloads</th>
    <th>Action</th>
  </tr>
  @foreach(var item in Model)
  {
    <tr>
      <td>@item.FileID</td>
      <td>@item.FileDisplayName</td>
      <td>@item.FileType</td>
      <td>@item.DownloadCount</td>
      <td>@Html.ActionLink("Download", "DownloadFile", 
           new { id=item.FileID })</td>
    </tr>
  }
  </table>
...

Notice the code marked in bold letters. The ActionLink() call points to DownloadFile() action method and passes FileID as a route parameter to this method. You will write this method shortly.

The following figure shows how the table looks like in the browser:

Sending files from the server to the client

Now comes the important part - the DownloadFile() action method. This method receives FileID as a parameter, increments the download count for that file and sends the file to the client for download. The DownloadFile() is shown below:

public FilePathResult DownloadFile(int id)
{
    using (FilesDbEntities db = new FilesDbEntities())
    {
        FileDownloadDemo.Models.File file = db.Files.Find(id);
        file.DownloadCount++;
        db.SaveChanges();
        string downloadName=file.FileDisplayName + 
          VirtualPathUtility.GetExtension(file.FilePath);
        return File(file.FilePath, file.FileType, downloadName);
    }
}

The DownloadFile() method increments the DownloadCount column value and calls SaveChanges() to save the changes back to the database. It then computes a file name that is displayed in the "Save As" dialog. Notice how VirtualPathUtility class is used while forming the file name. This file name can be anything of your choice. I am using the name same as the FileDisplayName value. The DownloadFile() method returns a FilePathResult (instead of usual ActionResult). To wrap a file in FilePathResult and send it to the client you use File() method. The File() method takes three parameters - virtual path of the file, MIME type of the file and name to be displayed in the "Save As" dialog.

If you run the application and click on the Download link you should see something like this:

Notice that the "Save As" dialog is showing the file name as per the third parameter supplied to the File() method.

Other variations of File() method

There are a couple of more variations of the File() method. These variations basically allow you to alter the source of the file content. In the preceding example the source of file content was a physical disk file. What if a file is stored directly in the database as a binary (BLOB) data? Or what if a file is being generated programmatically on-the-fly based on some logic? The other two variations of File() cover these possibilities.

Consider the following code that shows a DownloadBytes() action method.

public FileContentResult DownloadBytes(int id)
{
    using (FilesDbEntities db = new FilesDbEntities())
    {
       ...
       string path=VirtualPathUtility.ToAbsolute(file.FilePath);
       byte[] bytes = System.IO.File.ReadAllBytes(path);
       return File(bytes, file.FileType, downloadName);
    }
}

Here the file content is generated as a byte array (a more realistic case would be when a file is stored in the database). The byte array is then passed to the first parameter of File() method. Note that the return type of DownloadBytes() is FileContentResult.

Now consider one more variation.

public FileStreamResult DownloadStream(int id)
{
    using (FilesDbEntities db = new FilesDbEntities())
    {
       ...
       string path = VirtualPathUtility.ToAbsolute(file.FilePath);
       System.IO.Stream stream = System.IO.File.OpenRead(path);
       return File(stream, file.FileType, downloadName);
    }
}

Here the file is opened into a Stream using OpenRead() method (a more realistic case is when a file is generated on-the-fly on the server). The Stream is then passed to the first parameter of the File() method. Note that the return type of DownloadStream() is FileStreamResult.

That's it for now! Keep coding!!


Bipin Joshi is a software consultant, an author and a yoga mentor having 22+ years of experience in software development. He also conducts online courses in ASP.NET MVC / Core and Design Patterns. He is a published author and has authored or co-authored books for Apress and Wrox press. Having embraced the Yoga way of life he also teaches Meditation and Mindfulness to interested individuals. To know more about him click here.

Get connected : Twitter  Facebook  Google+  LinkedIn

Posted On : 16 March 2015


Tags : ASP.NET Data Access MVC C#