Stay up to date with weekly industry insights

the latest trends in web design, inbound marketing, mobile strategy and more...

Build a Handler to Force the Download of Media Files in Episerver

build-a-handler-to-force-the-download-of-media-files-in-episerver

While working on a recent Episerver build, I came across an interesting problem to solve. The application I was working on was a brand repository where users can download different types of files. The problem was that some of the files opened in the browser window instead of downloading to the user's computer. One of the weird things things was that this was not consistent behavior. Depending on the browser and operating system, sometimes files opened in the browser and sometimes they were downloaded. 

One thing that I tried initially, simply adding the attribute download to the <a> tag of the link, gave me better results, but it still wasn't consistent. I needed a solution to force a file to download regardless of browser or operating system. This turned out to be fairly easy to achieve. I ended up creating a handler class called Download.ashx and changing the structure of the download links to point to this file. 

First, I created an interface for all media types I want to force downloads on to inherit: 

	
namespace WSOL.Business.Interfaces
{
     using System;

	 public interface IDownloadable
     {
          Guid GetContentGUID();
     }
}
	

The reason we need the content GUID you will see in the code for the handler, for now, let's take a look at how I implemented this interface on my media type:

	
namespace WSOL.Models.Media
{
	using EPiServer.Core;
	using EPiServer.DataAnnotations;
	using EPiServer.Framework.DataAnnotations;
	using WSOL.Business.Interfaces;
	using System;

    [ContentType(GUID = "290C527A-4370-42A5-AAE1-46C9F125403C")]
    [MediaDescriptor(ExtensionString = "pdf")]
    public class PdfFile : MediaData, IDownloadable
    {
        #region Interface Implementation

        public Guid GetContentGUID()
        {
            return this.ContentGuid;
        }

        #endregion
    }
}
	

Now that we have this, let's take a look at the Download.ashx handler class:

	
namespace WSOL
{
	using EPiServer;
	using EPiServer.Core;
	using EPiServer.Framework.Blobs;
	using EPiServer.ServiceLocation;
	using System;
	using System.IO;
	using System.Web;

    /// <summary>
    /// Handler to force download of media files
    /// </summary>
    public class Download : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {           
            string file = context.Request.QueryString["file"];
            if (!string.IsNullOrEmpty(file))
            {
                var id = new Guid(file);
                var content = ContentLoader.Service.Get(id);
                var download = content as MediaData;
                if (download != null)
                {
                    var blob = download.BinaryData as FileBlob;              
                    if (blob != null)
                    {
                        string routeSegment = download.RouteSegment;
                        string extension = Path.GetExtension(blob.FilePath) ?? "";
                        string downloadFileName = routeSegment.EndsWith(extension) ? routeSegment : routeSegment + extension;
                        
                        // Set the ContentType to "application/octet-stream" which covers any type of file
                        context.Response.ContentType = "application/octet-stream";
                        context.Response.AddHeader("content-disposition", "attachment;filename=" + Path.GetFileName(downloadFileName));
                        context.Response.TransmitFile(blob.FilePath);
                    }
                }
            }
        }

        public Injected <IContentLoader> { get; set; }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}
	

This code is pretty straight-forward, so, let's walk through what is happening. First, I'm pulling the content GUID from the file querystring parameter. Then, I am using that to create a new GUID, which I am passing to IContentLoader to get an instance of the content. From there, I am casting IContent to MediaData, which then gets cast to a FileBlob. Once I have this, I have everything needed to setup the response context and force the file to download. 

The last piece is generating the proper URL for the download in the view. For this, I created an IDownloadable property in my view model. The page model then supplies a content reference to the media data, which I then cast to IDownloadable:

	
namespace WSOL.Models.ViewModels
{
	using EPiServer.Core;
	using WSOL.Business.Interfaces;
	using WSOL.Models.Pages;
	using WSOL.EPiServerCms.Web.Extensions;

    public class ProductDetailViewModel : PageViewModel
    {
        public ProductDetailViewModel(ProductDetailPageData currentPage)
            : base(currentPage)
        {                       
            if (currentPage.MedicationGuideDownload != null)
            {
                MedicationGuide = currentPage.MedicationGuideDownload.GetContent() as IDownloadable;
            }
        }
        
        public IDownloadable MedicationGuide { get; set; }
    }
}
	

Now, in my view, if the download exists, I point the URL to my handler and append the media item's GUID as the file querystring parameter:


<li class="sidebar-link-list-item">
    <i class="fa fa-angle-right"></i>
    <a href="~/Download.ashx?file=@Model.MedicationGuide.GetContentGUID()">Medication Guide</a>
</li>

That's all there is to it! This example is based around Episerver media types, but, this approach can easily be adopted for any .NET based application. If you have questions about the code, or if you want to know how we can use approaches like this to help you with your next web application, leave a comment below.

About the Author

John McKillip
John McKillip
As one of WSOL's Web Developers, John builds enterprise-level web solutions that are fast and scalable for a variety of clients and industries. He is passionate about all things computer science related, especially building web applications with .NET MVC. He is also very interested in user-centered, responsive design and keeping up with the latest front-end development techniques.