<img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1639164799743833&amp;ev=PageView&amp;noscript=1">
Diagram Views

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

John McKillip
#Episerver, #Code
Published on February 13, 2018
warren-wong-323107-unsplash-1

We show you how to build an Episerver handler to force a file download regardless of browser/operating system.

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 Diagram.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 Diagram.Models.Media
{
	using EPiServer.Core;
	using EPiServer.DataAnnotations;
	using EPiServer.Framework.DataAnnotations;
	using Diagram.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 Diagram
{
	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 Diagram.Models.ViewModels
{
	using EPiServer.Core;
	using Diagram.Business.Interfaces;
	using Diagram.Models.Pages;
	using Diagram.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.