Streaming file download from Console

Topics: General, Troubleshooting
Sep 7, 2011 at 3:22 PM

I have added a 'Download PDF of page' command to our install of composite by attaching an action to the IPage datatype in the tree definitions and using a customurlaction that opens a page that performs a conversion of the current page and streams out the result:

 

Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "attachment;filename=" + filename);
Response.BinaryWrite(fileData);

 

The download works fine but following this the cms is locked (perpetual timer) and has to be refreshed.

Is there any way around this, or would it be better to save the converted PDF to disk and provide a normal link to it, rather than trying to push it out of the page request?

Thanks

Coordinator
Sep 8, 2011 at 6:32 PM

"customurlaction" isn't suitable for downloading files. I wrote some code that does similar job a year ago, I'll try to find it and post it here tomorrow.

Coordinator
Sep 9, 2011 at 7:56 AM
Edited Sep 9, 2011 at 7:30 PM

Here's the code (updated)

using System;
using System.Collections.Generic;
using Composite.C1Console.Actions;
using Composite.C1Console.Elements;
using Composite.C1Console.Elements.Plugins.ElementActionProvider;
using Composite.C1Console.Events;
using Composite.C1Console.Security;
using Composite.Data;
using Composite.Core.Extensions;
using Composite.Core.ResourceSystem;
using Composite.Core.ResourceSystem.Icons;
using Composite.Data.Types;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;

namespace XXXXXXXX
{
    /// <summary>
    /// Adds "Download as PDF" button
    /// </summary>
    [ConfigurationElementType(typeof(NonConfigurableElementActionProvider))]
    public class DownloadAsPdfActionProvider : IElementActionProvider
    {
        private static readonly string DownloadUrl = "/Renderers/Pdf.aspx?pageId={0}";

        private static readonly List<ElementAction> EmptyActionList = new List<ElementAction>();
        private static readonly ActionGroup PrimaryFileActionGroup = new ActionGroup(ActionGroupPriority.PrimaryHigh);
        public static ResourceHandle DownloadFile { get { return GetIconHandle("media-download-file"); } }


        public IEnumerable<ElementAction> GetActions(EntityToken entityToken)
        {
            if (entityToken == null
               || !(entityToken is DataEntityToken)
               || (entityToken as DataEntityToken).InterfaceType != typeof(IPage))
            {
                return EmptyActionList;
            }

            return new[] {new ElementAction(new ActionHandle(new DownloadAsPdfActionToken()))
                {
                    VisualData = new ActionVisualizedData
                    {
                        Label = "Download as PDF",
                        ToolTip = string.Empty,
                        Icon = DownloadFile,
                        Disabled = false,
                        ActionLocation = new ActionLocation
                        {
                            ActionType = ActionType.Add, // For sorting purpose
                            IsInFolder = false,
                            IsInToolbar = true,
                            ActionGroup = PrimaryFileActionGroup
                        }
                    }
                }};
        }

        [ActionExecutor(typeof(DownloadAsPdfActionExecutor))]
        public sealed class DownloadAsPdfActionToken : ActionToken
        {
            static private readonly IEnumerable<PermissionType> _permissionTypes = new[] { PermissionType.Read };

            public override IEnumerable<PermissionType> PermissionTypes
            {
                get { return _permissionTypes; }
            }


            public override string Serialize()
            {
                return "DownloadAsPdf";
            }


            public static ActionToken Deserialize(string serializedData)
            {
                return new DownloadAsPdfActionToken();
            }
        }

        public sealed class DownloadAsPdfActionExecutor : IActionExecutor
        {
            public FlowToken Execute(EntityToken entityToken, ActionToken actionToken, FlowControllerServicesContainer flowControllerServicesContainer)
            {
               DataEntityToken dataEntityToken = entityToken as DataEntityToken;
               IPage page = dataEntityToken.Data as IPage;
               string pageId = page.Id.ToString();
                  string currentConsoleId = flowControllerServicesContainer.GetService<IManagementConsoleMessageService>().CurrentConsoleId;
                  string url = DownloadUrl.FormatWith(pageId);
                  ConsoleMessageQueueFacade.Enqueue(new DownloadFileMessageQueueItem(url), currentConsoleId);
                  return null;
             }
         }
          private static ResourceHandle GetIconHandle(string name)
         {
             return new ResourceHandle(BuildInIconProviderName.ProviderName, name);
         }
     }
 }
This class has to be registered in Composite.config
  <Composite.Elements.Plugins.ElementActionProviderConfiguration>
    <ElementActionProviderPlugins>
      <add name="DownloadAsPdfActionProvider" type="XXXXXXXX.DownloadAsPdfActionProvider, App_Code" />
....
Sep 9, 2011 at 2:22 PM

That's fantastic, got it working.. thanks napernik.

I had to make the following update to the DownloadAsPdfActionExecutor class to get the pageID:

        public sealed class DownloadAsPdfActionExecutor : IActionExecutor
        {
            public FlowToken Execute(EntityToken entityToken, ActionToken actionToken, FlowControllerServicesContainer flowControllerServicesContainer)
            {
                DataEntityToken dataEntityToken = entityToken as DataEntityToken;
                IPage page = dataEntityToken.Data as IPage;
                string pageId = page.Id.ToString();

                string currentConsoleId = flowControllerServicesContainer.GetService().CurrentConsoleId;

                string url = DownloadUrl.FormatWith(pageId);

                ConsoleMessageQueueFacade.Enqueue(new DownloadFileMessageQueueItem(url), currentConsoleId);

                return null;
            }
        }

Coordinator
Sep 9, 2011 at 7:31 PM

Good to hear. I updated the example with your fix

Sep 13, 2011 at 6:38 AM

Cool namespace. I think I'll use that on my next project. ;-)