Thursday, July 22, 2010

File Download Dialog Box for Sitecore Media Item


It is not a secret that Sitecore CMS can deliver media content in a dynamic fashion.
You can do pretty sick things with our MediaRequestHandler (as an example of that, check out what Alistair Deneys done with it in the past).
Today, however, I wanted to cover a fairly easy aspect of this functionality.

Specifically, the way you can force Media handler to force download of a requested resource, rather than having it being opened in the browser.
I cannot describe the technical challenge any better than Microsoft KB article explains it.
Basically, it all boils down to sending a specific HTTP header, for example:
Content-disposition: attachment; filename=fname.ext
Now good news is that with Sitecore you are able to control it via configuration and on MIME type basis. What you do is go to web.config and set “forceDownload” parameter to true on specific media type, for example, PDF:
<mediaType name="PDF file" extensions="pdf">
   <mimeType>application/pdf</mimeType>
   <forceDownload>true</forceDownload>
   <sharedTemplate>system/media/unversioned/pdf</sharedTemplate>
   <versionedTemplate>system/media/versioned/pdf</versionedTemplate>
</mediaType>

While this is nice and extremely convenient, what if you need more granularity? For example, have certain JPEGs being forced to download?
Sitecore’s answer – no problem!
Here is what you can do:

1. Create a custom MediaRequestHandler based on default one:
using System.Web;
using Sitecore.Resources.Media;

namespace Sitecore.StarterKit.Media
{
    public class ForceDownloadMediaHandler : MediaRequestHandler
    {
        public override void ProcessRequest(HttpContext context)
        {
            ProcessForceDownload(context);
            base.ProcessRequest(context);
        }

        private static void ProcessForceDownload(HttpContext context)
        {
            if (context.Request.QueryString["force"] == "1")
            {
                var request = MediaManager.ParseMediaRequest(context.Request);
                if (request != null)
                {
                    var media = MediaManager.GetMedia(request.MediaUri);
                    if (media != null)
                    {
context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + 
media.MediaData.MediaItem.Name + "." + media.MediaData.Extension + "\"");
                    }
                }
            }
        }
    }
}
2. Compile, place the assembly in /bin folder as usual.
3. Replace the default MediaRequestHandler with your custom one in web.config:
- Depending on the configuration,  under <system.webServer>/<handlers>:
<add verb="*" path="sitecore_media.ashx" type="Sitecore.StarterKit.Media.ForceDownloadMediaHandler, Sitecore.Starterkit" name="Sitecore.MediaRequestHandler" /> - And/or under <system.web>/<httpHandlers>:
<add verb="*" path="sitecore_media.ashx" type="Sitecore.StarterKit.Media.ForceDownloadMediaHandler, Sitecore.Starterkit" />
4. When you render a link to a resource, append the “force=1” query string.
5. Use it:
http://www.site.com/~/media/docs/installguide.ashx?forcedownload=1
That’s all, folks!
Thanks goes to Alexey for the source code :-)

6 comments:

Mark Anthony Vinculado said...

Thanks for this, I'm a new in sitecore dev you articles help me.

Tohams said...

Hey, Alex. I've copied /sitecore/templates/sysetm/media/versioned/file to make my own template that has some of those original fields and some additional ones while leaving the file blob in place. So now I'm using the item directly as content and can still have a download link to the attached file: http://mysite/~/media/74021ABC862145E0A086B46FDB8EAB58.ashx. I've followed the directions on this post, but don't think it's hitting my class (it doesn't seem to hit the overridden ProcessRequest breakpoint I put in). Any hints?

Thanks!

Anonymous said...

I think you should re-implement the same code that was on the AddItemReferences then add your code to it , this is how it worked with me , thaaaaanks for ur post keep up the good posts :)

josh5699 said...

It doesn't look like there's a clear way to remove the existing header. I get this erro on the client: Duplicate headers received from server

josh5699 said...

It doesn't look like there's a clear way to remove the existing header. I get this erro on the client: Duplicate headers received from server

Unknown said...

I know this is an old post, but we have a production issue with Chrome in that two content-disposition headers are placed in the response.

Chrome now throws and error when clicking links with the force=1 query string, as base process request adds the content disposition header after the custom handler has already added one.

Changing the constructor such that the base method is executed first resolves the issue.