Friday, 23 September 2016

Customizing PDF download behaviour for Sitecore multisite implementation

Leave a Comment
Recently I got a requirement from my client where I have to customize PDF rendering behaviour for Sitecore multisite implementation. For few websites, PDF files should be rendered in web browser itself and for few websites, web browser should prompt the user to save the PDF files. I am working with Sitecore 8.2 initial release.

PDF rendering behaviour is being managed by <forceDownload> element for PDF file media type under \mediaLibrary\mediaTypes configuration node. This setting can be found in Sitecore.Config file in \Website\App_Config folder. A true value for the forceDownload element causes Sitecore to apply an HTTP "Content-Disposition = attachment; filename=" header when linking to the .ashx URL of the media item, causing the browser to prompt the user to open/save as rather than opening the media item in the browser.


Disabling/Enabling forceDownload behaviour for PDF file will impact all the websites but I need flexibility to disable/enable this behaviour across multiple websites. This behaviour can be achieved in two ways:
  1. Forcing download on client side using HTML 5 download attribute
  2. Override MediaRequestHandler and modify "Content-Disposition" in context response header
  1. Forcing download on client side using HTML 5 download attribute
    Use HTML5 download attribute on your anchor links. The download attribute specifies that the target will be downloaded when a user clicks on the hyperlink. However this attribute is not supported by all the browsers.
  2. Override MediaRequestHandler and modify "Content-Disposition" in context response header
    Below are the steps to implement the desired behaviour across multiple websites:
    • Create a new class library project in visual studio.
    • Add reference to Sitecore.Kernel.dll
    • Add a custom attribute in your site definition. For example, "PdfForceDownloadDisabled"
      <site name="mysite" patch:before="site[@name='website']"
                  virtualFolder="/"
                  physicalFolder="/"
                  rootPath="/sitecore/content"
                  startItem="/MySite"
                  database="web"
                  domain="extranet"
                  allowDebug="true"
                  cacheHtml="true"
                  htmlCacheSize="50MB"
                  enablePreview="true"
                  enableWebEdit="true"
                  enableDebugger="true"
           PdfForceDownloadDisabled="true"
           hostName="local.mysite.com"
                  disableClientData="false"/>
    • Implement a custom Extension method :
      using Sitecore.Diagnostics;
      using Sitecore.Sites;
      using System;
      
      namespace CustomMedia
      {
          public static class UtilityManager
          {
              public static bool PdfForceDownloadDisabled(this SiteContext site)
              {
                  Assert.IsNotNull(site, "Site cannot be null");
      
                  try
                  {               
                      string PdfForceDownloadDisabled = site.Properties["PdfForceDownloadDisabled"];
                      if (!String.IsNullOrEmpty(PdfForceDownloadDisabled))
                          return Convert.ToBoolean(PdfForceDownloadDisabled); 
                  }
                  catch (Exception)
                  {
                      return false;
                  }
                  return false;
              }       
          }
      }
      
    • Override "protected override bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media)" method and modify "Content-Disposition" in context response header.
      using Sitecore;
      using Sitecore.Configuration;
      using Sitecore.Data.Items;
      using Sitecore.Diagnostics;
      using Sitecore.Events;
      using Sitecore.Resources.Media;
      using Sitecore.Resources.Media.Streaming;
      using Sitecore.Web;
      using System.Web;
      
      namespace CustomMedia
      {
          public class MediaRequestHandler : Sitecore.Resources.Media.MediaRequestHandler
          {
              protected override bool DoProcessRequest(HttpContext context, MediaRequest request, Sitecore.Resources.Media.Media media)
              {
                  Assert.ArgumentNotNull(context, "context");
                  Assert.ArgumentNotNull(request, "request");
                  Assert.ArgumentNotNull(media, "media");
                  if (this.Modified(context, media, request.Options) == Tristate.False)
                  {
                      Event.RaiseEvent("media:request", new object[] { request });
                      this.SendMediaHeaders(media, context);
                      context.Response.StatusCode = 0x130;
                      return true;
                  }
                  this.ProcessImageDimensions(request, media);
      
                  MediaStream mediaStream = media.GetStream(request.Options);
      
                  if (mediaStream == null)
                  {
                      return false;
                  }
                  Event.RaiseEvent("media:request", new object[] { request });
                  if (Settings.Media.EnableRangeRetrievalRequest && Settings.Media.CachingEnabled)
                  {
                      using (mediaStream)
                      {
                          this.SendMediaHeaders(media, context);
                          new RangeRetrievalResponse(RangeRetrievalRequest.BuildRequest(context, media), mediaStream).ExecuteRequest(context);
                          //Update Header   - Custom Add             
                          this.UpdateHeader(media, context, mediaStream);
                          return true;
                      }
                  }
                  this.SendMediaHeaders(media, context);
                  this.SendStreamHeaders(mediaStream, context);
                  using (mediaStream)
                  {
                      context.Response.AddHeader("Content-Length", ((long)mediaStream.Stream.Length).ToString());
                      WebUtil.TransmitStream(mediaStream.Stream, context.Response, Settings.Media.StreamBufferSize);
                  }
                  return true;
              }
      
              private void ProcessImageDimensions(MediaRequest request, Sitecore.Resources.Media.Media media)
              {
                  int num;
                  int num2;
                  Assert.ArgumentNotNull(request, "request");
                  Assert.ArgumentNotNull(media, "media");
                  Item innerItem = media.MediaData.MediaItem.InnerItem;
                  int.TryParse(innerItem["Height"], out num2);
                  int.TryParse(innerItem["Width"], out num);
                  bool flag = false;
                  int maxHeight = Settings.Media.Resizing.MaxHeight;
                  if ((maxHeight != 0) && (request.Options.Height > System.Math.Max(maxHeight, num2)))
                  {
                      flag = true;
                      request.Options.Height = System.Math.Max(maxHeight, num2);
                  }
                  int maxWidth = Settings.Media.Resizing.MaxWidth;
                  if ((maxWidth != 0) && (request.Options.Width > System.Math.Max(maxWidth, num)))
                  {
                      flag = true;
                      request.Options.Width = System.Math.Max(maxWidth, num);
                  }
                  if (flag)
                  {
                      Log.Warn($"Requested image exceeds allowed size limits. Requested URL:{request.InnerRequest.RawUrl}", this);
                  }
              }
      
              private void UpdateHeader(Sitecore.Resources.Media.Media media, HttpContext context, MediaStream stream)
              {
                  if (media.Extension.ToLower() == "pdf" && Sitecore.Context.Site.PdfForceDownloadDisabled())
                  {
                      context.Response.Headers["Content-Disposition"] = "inline; filename=\"" + stream.FileName + "\"";
                  }
              }
          }
      }
      
    • In web.config, replace <add verb="*" path="sitecore_media.ashx" type="Sitecore.Resources.Media.MediaRequestHandler, Sitecore.Kernel" name="Sitecore.MediaRequestHandler" />  with the following line:
      <add verb="*" path="sitecore_media.ashx" type="CustomMedia.MediaRequestHandler, {your assembly name}" name="Sitecore.MediaRequestHandler" />
    • Deploy your assembly in bin folder of your Sitecore website.
Comments and suggestions are most welcome. Happy coding!

0 comments :

Post a comment