It's ok. Be different.

SharePoint 2010 Download as Zip File Custom Ribbon Action

So I was bored and decided I wanted to learn a little more about the new SharePoint 2010 UI Framework. There are some really cool things you can do with the new framework and I wanted to put some of them to use but at the same time create something that’s useful for other people. I remember a while back someone asking me to write code for MOSS 2007 that would allow users to download files down to their computer as a zip file because they didn’t like having to download each file one by one. For some reason or another, I never got around to it – I think I told the person that they could download multiple files from SharePoint using Windows Explorer and, although it wasn’t exactly what they were looking for, it at least got the job done. But I decided recently that this might not be a bad enhancement to add to the UI in 2010 so that’s what I set out to build today.

The first thing to look at is my Visual Studio 2010 project. I will discuss all the important files that are part of this project. Below is a screenshot of my project:


The first thing I did was to create a new SharePoint 2010 Empty SharePoint Project called DeviantPoint.DownloadZip. Then, I added a reference to ICSharpCode.SharpZipLib.dll which is a part of SharpZipLib(a .NET library that is well-used for working with Zip files). I then created a helper class, ZipBuilder, used to actually create the zip file. I created this helper class with the intent that I could reuse this elsewhere, not just for this project.

Basically, when an instance of this class is constructed, you need to pass in a stream that will be used to write the contents of the zip file to. This could be any type stream (FileStream, MemoryStream, etc). There are a couple of helper methods in this class that allow you to add files and folders and method that “finalizes” the zip file. This Finalize() method must always be called to ensure that the zip file is written out correctly. This class also implements the IDisposable pattern since it is handling streams.

This is the code for the ZipBuilder class:


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.IO;
   6: using ICSharpCode.SharpZipLib.Zip;
   7: using ICSharpCode.SharpZipLib.Core;
   9: namespace DeviantPoint.DownloadZip
  10: {
  11:     public class ZipFileBuilder : IDisposable
  12:     {
  13:         private bool disposed = false;
  15:         ZipOutputStream zipStream = null;
  16:         protected ZipOutputStream ZipStream
  17:         {
  18:             get { return zipStream; }
  20:         }
  22:         ZipEntryFactory factory = null;
  23:         private ZipEntryFactory Factory
  24:         {
  25:             get { return factory; }
  26:         }
  29:         public ZipFileBuilder(Stream outStream)
  30:         {
  31:             zipStream = new ZipOutputStream(outStream);
  32:             zipStream.SetLevel(9); //best compression
  34:             factory = new ZipEntryFactory(DateTime.Now);
  35:         }
  37:         public void Add(string fileName, Stream fileStream)
  38:         {
  39:             //create a new zip entry            
  40:             ZipEntry entry = factory.MakeFileEntry(fileName);
  41:             entry.DateTime = DateTime.Now;
  42:             ZipStream.PutNextEntry(entry);
  44:             byte[] buffer = new byte[65536];
  46:             int sourceBytes;
  47:             do
  48:             {
  49:                 sourceBytes = fileStream.Read(buffer, 0, buffer.Length);
  50:                 ZipStream.Write(buffer, 0, sourceBytes);
  51:             }
  52:             while (sourceBytes > 0);
  55:         }
  57:         public void AddDirectory(string directoryName)
  58:         {
  59:             ZipEntry entry = factory.MakeDirectoryEntry(directoryName);
  60:             ZipStream.PutNextEntry(entry);
  61:         }
  63:         public void Finish()
  64:         {
  65:             if (!ZipStream.IsFinished)
  66:             {
  67:                 ZipStream.Finish();
  68:             }
  69:         }
  71:         public void Close()
  72:         {
  73:             Dispose(true);
  74:             GC.SuppressFinalize(this);
  75:         }
  77:         public void Dispose()
  78:         {
  79:             this.Close();
  80:         }
  82:         protected virtual void Dispose(bool disposing)
  83:         {
  84:             if (!disposed)
  85:             {
  86:                 if (disposing)
  87:                 {
  88:                     if (ZipStream != null)
  89:                         ZipStream.Dispose();
  90:                 }
  91:             }
  93:             disposed = true;
  94:         }
  95:     }
  96: }

The next thing I wrote was SPExtensions.cs, a class for adding extension methods to some of the Microsoft.SharePoint objects. This class basically just adds a few simple helper methods to the SPListItem class and the SPList class. For the SPListItem class, I just added a method to determine if the SPListItem instance is actually a folder and for the SPList class, I added a method to determine if the list is actually a document library.

The code for SPExtensions is below:


   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Runtime.CompilerServices;
   6: using Microsoft.SharePoint;
   8: namespace DeviantPoint.DownloadZip
   9: {
  10:     public static class SPExtensions
  11:     {
  12:         public static bool IsFolder(this SPListItem item)
  13:         {
  14:             return (item.Folder != null);
  15:         }
  17:         public static bool IsDocumentLibrary(this SPList list)
  18:         {
  19:             return (list.BaseType == SPBaseType.DocumentLibrary);
  20:         }
  21:     }
  22: }

The next thing I did was to add a SharePoint Mapped Folder to my project mapping to the Layouts directory located in the SharePoint root. When you add a mapped folder, Visual Studio will automatically create a sub-folder in that mapped folder with the same name as your project. This is a good thing as you don’t want to be mixing up all your project files with all of the files that come out of the box from SharePoint.

After I had my Layouts mapped folder and subfolder created, I added a SharePoint 2010 Application Page item to the sub-folder called DownloadZip.aspx. The purpose of this application page is to actually handle the request from the client to build the zip file and send it back down to the client. Having an application page handle this is the same technique that is used with the ‘Download a Copy’ action button you see in the SharePoint 2010 ribbon. Basically, a POST request from a client is sent to my DownloadZip.aspx page and this page takes care of packaging up a zip file and sending it down to the client’s browser. This page expects two parameters:

  • sourceUrl –the full url of the document library (and folder, if inside of a subfolder) where the request is being made from
  • itemIDs – a semi-colon delimited list of the SPListItem IDs that should be included as part of the zip file. Note that folders also have ids so if a folder is selected, that folder’s id would also be sent.

The code-behind for this application page basically takes the list of item ids and for each item id, goes and grabs the corresponding file from the document library in SharePoint and, using the ZipBuilder class, packages it up as a zip file. If one of the items that was selected is actually a folder, it will create that folder in the zip file as well and put all the items that are in that SharePoint folder into the corresponding zip file folder. It will also traverse through all the sub-folders in the hierarchy.

Below is the code-behind for the DownloadZip.aspx application page (there is nothing I added to the Download.aspx file itself):


   1: using System;
   2: using System.IO;
   3: using System.Web;
   4: using Microsoft.SharePoint;
   5: using Microsoft.SharePoint.WebControls;
   7: namespace DeviantPoint.DownloadZip.Layouts.DeviantPoint.DownloadZip
   8: {
   9:     public partial class DownloadZip : LayoutsPageBase
  10:     {
  11:         protected void Page_Load(object sender, EventArgs e)
  12:         {            
  13:             string fullDocLibSourceUrl = Request.Params["sourceUrl"];
  14:             if (string.IsNullOrEmpty(fullDocLibSourceUrl)) return;
  16:             string docLibUrl = fullDocLibSourceUrl.Replace(SPContext.Current.Site.Url, "");
  18:             SPList list = SPContext.Current.Web.GetList(docLibUrl);
  19:             if (!list.IsDocumentLibrary()) return;
  21:             string pItemIds = Request.Params["itemIDs"];
  22:             if (string.IsNullOrEmpty(pItemIds)) return;
  24:             SPDocumentLibrary library = (SPDocumentLibrary)list;
  26:             string[] sItemIds = pItemIds.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
  27:             int[] itemsIDs = new int[sItemIds.Length];
  28:             for (int i = 0; i < sItemIds.Length; i++)
  29:             {
  30:                 itemsIDs[i] = Convert.ToInt32(sItemIds[i]);
  31:             }
  33:             if (itemsIDs.Length > 0)
  34:             {
  35:                 using (MemoryStream ms = new MemoryStream())
  36:                 {
  37:                     using (ZipFileBuilder builder = new ZipFileBuilder(ms))
  38:                     {
  39:                         foreach (int id in itemsIDs)
  40:                         {
  41:                             SPListItem item = library.GetItemById(id);
  42:                             if (item.IsFolder())
  43:                                 AddFolder(builder, item.Folder, string.Empty);
  44:                             else
  45:                                 AddFile(builder, item.File, string.Empty);
  46:                         }
  48:                         builder.Finish();
  49:                         WriteStreamToResponse(ms);
  50:                     }
  51:                 }
  52:             }
  54:         }
  56:         private static void AddFile(ZipFileBuilder builder, SPFile file, string folder)
  57:         {
  58:             using (Stream fileStream = file.OpenBinaryStream())
  59:             {
  60:                 builder.Add(folder + "\\" + file.Name, fileStream);
  61:                 fileStream.Close();
  62:             }
  63:         }
  65:         private void AddFolder(ZipFileBuilder builder, SPFolder folder, string parentFolder)
  66:         {
  67:             string folderPath = parentFolder == string.Empty ? folder.Name : parentFolder + "\\" +folder.Name;
  68:             builder.AddDirectory(folderPath);
  70:             foreach (SPFile file in folder.Files)
  71:             {
  72:                 AddFile(builder, file, folderPath);
  73:             }
  75:             foreach (SPFolder subFolder in folder.SubFolders)
  76:             {
  77:                 AddFolder(builder, subFolder, folderPath);
  78:             }
  79:         }
  81:         private void WriteStreamToResponse(MemoryStream ms)
  82:         {
  83:             if (ms.Length > 0)
  84:             {
  85:                 string filename = DateTime.Now.ToFileTime().ToString() + ".zip";
  86:                 Response.Clear();
  87:                 Response.ClearHeaders();
  88:                 Response.ClearContent();
  89:                 Response.AddHeader("Content-Length", ms.Length.ToString());
  90:                 Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
  91:                 Response.ContentType = "application/octet-stream";
  93:                 byte[] buffer = new byte[65536];
  94:                 ms.Position = 0;
  95:                 int num;
  96:                 do
  97:                 {
  98:                     num = ms.Read(buffer, 0, buffer.Length);
  99:                     Response.OutputStream.Write(buffer, 0, num);
 100:                 }
 102:                 while (num > 0);
 104:                 Response.Flush();
 105:             }
 106:         }
 107:     }
 108: }

After creating the application page, I added a SharePoint 2010 Empty Element item called DownloadZip to my project. This is nothing more than an Elements.xml file that takes care of adding my custom action to ribbon (CustomAction.Location=”CommandUI.Ribbon”). By default, for document libraries, this is what the ribbon looks like:


I wanted to add my action inside of the area in the Documents tab, inside of the Copies group so to do this, for the CommandUIDefinition, I set the Location attribute to "Ribbon.Documents.Copies.Controls._children”. I also wanted it to appear right after the Download a Copy action so for the Button element’s Sequence attribute, I set the value to 15 (the Download a Copy button has a sequence of 10 and the Send To button has a sequence of 20 so I needed to set the sequence of my button to something in between). To understand where everything is placed and what the sequences are by default, you need to look at the file C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\TEMPLATE\GLOBAL\XML\CMDUI.xml. I also specified the icons I wanted to use for my action (these icons are also part of my project, located in a sub-directory of the Images SharePoint mapped folder) and I also set the TemplateAlias to “o1” so that my icon shows up large like Download a Copy does. I also define the actual command handler in this Elements.xml file by adding a CommandUIHandler element. The CommandAction attribute is used to specify what exactly the button is supposed to do and the EnabledScript attribute is used to determine whether or not the button/command is enabled. These two attributes’ values both point to javascript functions I define in a separate file (discussed later). Because I’m using a separate javascript file, I also have to add another CustomAction element in the Elements file that points to the location of my javascript file. This is the result:





Below is the full Elements.xml file:


   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Elements xmlns="">
   3:   <CustomAction Id="DeviantPoint.DownloadZip" Location="CommandUI.Ribbon">
   4:     <CommandUIExtension>
   5:       <CommandUIDefinitions>
   6:         <CommandUIDefinition Location="Ribbon.Documents.Copies.Controls._children">
   7:           <Button Id="Ribbon.Documents.Copies.DownloadZip"
   8:                   Command="DownloadZip"
   9:                   Sequence="15" 
  10:                   Image16by16="/_layouts/images/DeviantPoint.DownloadZip/zip_16x16.png" 
  11:                   Image32by32="/_layouts/images/DeviantPoint.DownloadZip/zip_32x32.png"
  12:                   Description="Download zip" LabelText="Download as Zip"
  13:                   TemplateAlias="o1"/>
  14:         </CommandUIDefinition>
  15:       </CommandUIDefinitions>
  16:       <CommandUIHandlers>
  17:         <CommandUIHandler
  18:           Command="DownloadZip"
  19:           CommandAction="javascript:downloadZip();"
  20:           EnabledScript="javascript:enable();"/>
  21:       </CommandUIHandlers>
  22:     </CommandUIExtension>
  23:   </CustomAction>
  24:   <CustomAction Id="Ribbon.Library.Actions.Scripts"
  25:                 Location="ScriptLink"
  26:                 ScriptSrc="/_layouts/DeviantPoint.DownloadZip/CustomActions.js" />
  27: </Elements>

Finally, I created the CustomActions.js file. This file is used to define the actions/behavior of my new ribbon button. The enable() function is used to determine whether or not my button is enabled. If there is at least one item selected, then my button is enabled. The downloadZip() function just starts off the download process. Actually, I could have probably written the javascript so I didn’t even need this function or calls to SP.ClientContext.executeQueryAsync() but I was just trying to get something done quickly and actually writing it this way gave me another place to show-off another one of the UI features, the Status. If the call to SP.ClientContext.executeQueryAsync() fails, then the onQueryFailed delegate is executed. The onQueryFailed() function uses the SP.UI.Status to display the error message, shown here:


The function onQuerySucceeded() is where the majority of the action happens. I use the SP.ListOperation.Selection object to get a list of the selected items. I then create a request to my DownloadZip.aspx application page and send that page the list of selected item ids as well as the current url (the url of the page the user is on). Like I said earlier, that application page takes care of packaging everything up as a zip and streaming it down to the browser.

Below is the code for CustomActions.js:


   1: function enable() {
   2:     var items = SP.ListOperation.Selection.getSelectedItems();
   3:     var itemCount = CountDictionary(items);
   4:     return (itemCount > 0);
   6: }
   8: function downloadZip() {
  10:     var context = SP.ClientContext.get_current();
  11: = context.get_site();
  12:     this.web = context.get_web();
  13:     context.load(;
  14:     context.load(this.web);
  15:     context.executeQueryAsync(
  16:         Function.createDelegate(this, this.onQuerySucceeded),
  17:         Function.createDelegate(this, this.onQueryFailed)
  18:     );    
  19: }
  21: function onQuerySucceeded() {
  23:     var items = SP.ListOperation.Selection.getSelectedItems();
  24:     var itemCount = CountDictionary(items);
  26:     if (itemCount == 0) return;
  28:     var ids = "";
  29:     for (var i = 0; i < itemCount; i++) {
  30:         ids += items[i].id + ";";
  31:     }
  33:     //send a request to the zip aspx page.
  34:     var form = document.createElement("form");
  35:     form.setAttribute("method", "post");
  36:     form.setAttribute("action", + this.web.get_serverRelativeUrl() + "/_layouts/deviantpoint.downloadzip/downloadzip.aspx");
  38:     var hfSourceUrl = document.createElement("input");
  39:     hfSourceUrl.setAttribute("type", "hidden");
  40:     hfSourceUrl.setAttribute("name", "sourceUrl");
  41:     hfSourceUrl.setAttribute("value", location.href);
  42:     form.appendChild(hfSourceUrl);
  44:     var hfItemIds = document.createElement("input")
  45:     hfItemIds.setAttribute("type", "hidden");
  46:     hfItemIds.setAttribute("name", "itemIDs");
  47:     hfItemIds.setAttribute("value", ids);
  48:     form.appendChild(hfItemIds);
  50:     document.body.appendChild(form);
  51:     form.submit();
  52: }
  54: function onQueryFailed(sender, args) {
  55:     this.statusID = SP.UI.Status.addStatus("Download as Zip:", 
  56:         "Downloading Failed: " + args.get_message() + " <a href='#' onclick='javascript:closeStatus();return false;'>Close</a>.", true);
  57:     SP.UI.Status.setStatusPriColor(this.statusID, "red");
  58: }
  60: function closeStatus() {
  61:     SP.UI.Status.removeStatus(this.statusID);
  62: }

So how does this actually all look when the user is using it? Below is the hierarchy of an example document library I have:


  • Documents
    • Folder A (Folder)
      • Subfolder in Folder A (Folder)
        • Sub Sub Folder (Folder)
          • Versioning Flow (Visio diagram)
        • Business Brief_SoW (Word document)
        • SoW_Phase1 (Word document)
      • Request Email (Text file)
      • Users and Roles (Excel file)
    • Issues (Excel file)
    • Product_Planning (Excel file)

The user has selected some documents and a sub-folder so my custom ribbon button is enabled:


The user clicks on this button and this feature executes and after it’s complete the user is prompted with this (note, the filename is a timestamp):


The user saves it down locally to his computer and sees all the files are there:


That’s it, code is done. Time to drink some beer (I was actually drinking some beer as I wrote this so forgive any mistakes :)).

WSP file (Site collection feature): DeviantPoint.DownloadZip.wsp (84.84 kb)

Visual Studio 2010 Project file: (317.01 kb)

Comments (50) -

  • GR

    6/9/2010 8:56:03 AM | Reply

    Thanks for this tutorial!
    It works really well.

    Can I put this button also in the context menu of a file or folder, in stead of in the ribbon?

  • GR

    6/9/2010 9:18:39 AM | Reply

    Thanks for this blogpost, was really helpful!

    Can I put the button in the contextmenu of the file or folder as well, rather than having it in the ribbon menu ?

  • c_manboy

    7/5/2010 10:15:36 AM | Reply

    I converted the webpart to vb in VS2010, but when I run it I get a FileNotFound exception when it tries to access the ICSharpCode.SharpZipLib dll.  It seems the dll is failing to be included in the package?  Do I need to set a property somewhere so the dll is included in the package?

  • c_manboy

    7/5/2010 11:56:23 PM | Reply

    THANK YOU!  This project paved the way for me to add a ribbon feature of my own that is desparately needed. I was stuck, for quite some time, on how to get the additional dll file included in the package, but eventually found that I needed to add it in package>advanced>additional assemblies.

  • GR

    8/13/2010 7:29:27 AM | Reply


    When I try this on my laptop with windows 7 64bit it works great (everything installed on the same machine: VS2010, SP2010, SQL Server)
    When I try this on our Windows Server 2008 Enterprise 64bit I get a runtime error on the SharpZipLib dll:

    Could not load file or assembly 'ICSharpCode.SharpZipLib, Version=, Culture=neutral, PublicKeyToken=1b03e6acf1164f73' or one of its dependencies. The system cannot find the file specified.

    On this line:
    using (ZipFileBuilder builder = new ZipFileBuilder(ms))

    What could be the issue here?
    The dll is there, the reference to the dll is there, it compiles just fine in Visual Studio, but at runtime it doesn't know how to find the dll anymore ?
    While on the Windows 7 machine it works just fine?

    Any idea?

  • GR

    8/13/2010 7:37:19 AM | Reply

    Ok never mind I missed the last reply here which was giving the solution already Smile
    I had to include the dll in the package first (package>advanced>additional assemblies)

  • Revathy

    8/31/2010 6:23:28 AM | Reply

    script is not firing any suggestions pls..............?

  • Revathy

    8/31/2010 6:24:51 AM | Reply

    It's showing error on my page .Script error line no:1137 and 1204

    what to do to solve it?

  • Rachel

    9/27/2010 10:02:28 AM | Reply

    This program is brilliant! i have been looking for this for a long time
    I know this might sound stupid (i am not really friendly with computers), how do i actually use the program you made?
    I clicked on both of those links but only the visual studio opens.
    WSP file does not seem to open as it keep says find it on the web...
    For visual studio, I downloaded the file, but there are lots of files...and i am confused which one to open.

    Somebody help me please!

  • Nico

    10/1/2010 10:08:27 AM | Reply

    This is a great tutorial

    However, in internet explorer, when downloadinig the file I get the information bar displaying something like 'this page is trying to send you a file ...', I have to click it and accept and then click the button again

    Is there a fix for this?

  • Stephen Lackey

    10/21/2010 11:27:39 AM | Reply

    I was just trying out your feature <Very nice i must say> but I have ran into a slight issue not with your project but rather its ability to work with Easy Tabs. (I have added the link to Easy Tabs below for other to try out if they would like) it is a neat tool. But when i try to DL a document from a Document library view that is nested in an Easy tab i get an error. "An unexpected error has occurred." This is most likely just an issue with the Easy Tabs but I was curious if you could at least point me in a direction on the solution. I will do all of the work myself but i thought with your knowledge of the project that you might have a solution direction I should take. It would appear to me that if the Download as zip would not be enabled when i select a document if there was a scope issue. To be honest it has me a little lost.
    Any help would be appreciated greatly!

  • Stephen Lackey

    10/29/2010 10:51:07 AM | Reply

    To update the issue in my previous post the issue is not with Easy Tabs it is actually the download solution it crashes any time you attempt to download a file from a webart view of a document library. Am i going about this all wrong? Should i not be creating views for my users? Please provide some feed back.

  • Purvi Shah

    11/9/2010 2:45:02 PM | Reply

    Hi everyone! First of all this is AN AWSOME FEATURE!! I have successfully deployed the feature on our farm. It works fine on the default site collection (http://<servername>), but not on other site collection (http://<servername>/sites/DealSites). I checked the event viewer and found this:

    Exception information:
        Exception type: FileNotFoundException
        Exception message: <nativehr>0x80070002</nativehr><nativestack></nativestack>There is no Web named "/sites/DealSites/sites/DealSites/_layouts/deviantpoint.downloadzip/downloadzip.aspx".

    How do I set it up so that it doesn't append the "/sites/DealSites" part of the URL?


  • Purvi Shah

    11/10/2010 11:17:08 AM | Reply

    Hi All, I installed the feature successfully on our sharepoint farm. It works on the default Site collection (http://servername) but not on the other site collection (http://servername/sites/collection1). Below is the Exception from the event viewer:

    Exception information:
        Exception type: FileNotFoundException
        Exception message: <nativehr>0x80070002</nativehr><nativestack></nativestack>There is no Web named "/sites/collection1/sites/collection1/_layouts/deviantpoint.downloadzip/downloadzip.aspx".

    How do I fix it so that it doesn't append the "sites/collection1" in the url again?


  • Jeeva

    5/3/2011 4:57:50 AM | Reply

    Hi Bart X. Tubalinal ,

    This is very good reference and i have installed this solution in my farm.
    Its working only on default site and its not working for the remainig sub-sites and other site collections.

    How do fix this issue.


  • dalleria

    5/24/2011 9:33:04 AM | Reply


    I have the same problem as Purvi Shah, I have a "FileNotFoundException".
    I think the problem is with the location of "downloadZip.aspx".
    Or with the url used for catch SPList.

    Unfortunately I was not enough good to debug it :s

  • Anton

    4/4/2012 2:45:00 PM | Reply

    Hi, so it seems that this solution is nice at the root level does not work in /sites/blah
    it seems like your code customActions.js line 36

    form.setAttribute("action", + this.web.get_serverRelativeUrl() + "/_layouts/deviantpoint.downloadzip/downloadzip.aspx"); + this.web.get_serverRelativeUrl + ...aspx.

    Ok so i opened your WSP and modified the code to take away the this.web.get_serverRelativeUrl() and it leaves me with


    a nasty file not found 404 error. so where does the _layouts/deviantpoint.downloadzip/downloadzip.aspx need to be in ?
    Can you please please point me in the right direction and tell me how to fix the code? thanks in advance! ill buy you beers!

  • Anton

    4/4/2012 6:08:01 PM | Reply

    Bart, can you please tell me how to fix the file not found issue when using this under /sites/mysitename
    i keep getting an error.
    I think the issue is in the jscript file and the aspx but i cant really figure it out, thanks in advance

  • Karen

    5/22/2012 1:44:35 AM | Reply

    Hi, the solution is very nice and it's exactly the thing I look for. Great work!

    I changed Ln36 in CustomAction.js to
    form.setAttribute("action", + "/_layouts/deviantpoint.downloadzip/downloadzip.aspx");

    and Ln16 in DownloadZip.aspx.cs to
    string docLibUrl = fullDocLibSourceUrl;

    Now, it works for both http://<server> and http://<server>/sites/<subsite>

  • Mateusz

    5/23/2012 12:38:06 AM | Reply

    When you have a site with an explicit inclusion, you also need to change the line 36.

    I have also noticed that the solution would not work if you added a document library as a web part on the home page (like it is in Team site template).
    So in line 24 I added:
    var listId = SP.ListOperation.Selection.getSelectedList();

    then in line 36 I modified:
    form.setAttribute("action", this.web.get_serverRelativeUrl() + "/_layouts/PJB.DownloadZip/DownloadZip.aspx?ListId=" + listId);

    Finally, in the downloadzip.aspx.cs file:

    string listId = Request.QueryString["ListId"].Replace("{", "").Replace("}","");
                Guid listGuid = new Guid(listId);


    SPList list = SPContext.Current.Web.Lists[listGuid];

  • Tony

    5/23/2012 2:31:10 AM | Reply

    Yes indeed the solution is very nice and works least for domain users, I have a customer with a mixed environment domain / FBA users.When FBA users that are site members try to use download as zip they get a permission error, however it does work without problems for FBA site admins or site owners.Anyone who has stumbled across this and found a solution ?

  • Mark

    6/15/2012 5:47:29 AM | Reply

    Would you post the .wsp with this fix please?

  • Todd

    6/18/2012 8:35:59 AM | Reply

    I'm receiving a message:
    - Download as Zip: Downloading Failed: Access Denied.  You do not have permission to perform this action or access this resource

    Any help would be greatly appreciated

  • Seyed

    7/27/2012 1:42:45 PM | Reply

    Thanks for this great post. I reviewed/deployed the code and found that if a documents are selected from another location than the hosting document library, then zipdownload.aspx fails. the issue is when it tries to resolve the document library location.
    This can be easily solved in the javascript code by getting the libId  and adding a form input for that:
    1) Get the lib id:
    var libId = SP.ListOperation.Selection.getSelectedList();
    2) Add a form input:
    var hfLibId = document.createElement("input");
                hfLibId.setAttribute("type", "hidden");
                hfLibId.setAttribute("name", "libId");
                hfLibId.setAttribute("value", libId);
    3) modify the aspx page to look at "libId" parameter instead of "sourceUrl"
    and then get the list based on the guid:
    string libid = Request.Params["libId"];
    SPList list = SPContext.Current.Web.Lists[new Guid(libid)];

  • Seema

    8/13/2012 2:49:57 AM | Reply


    Great post on multiple download.!!!!!

    Could u pls let me know how to refresh the page after download.....

  • Angie Guile

    8/13/2012 3:17:11 AM | Reply

    This is great but we are looking for something very similar that allows multiple file downloads but not via a zip - should just offer file dialog box and save all the files in the selected folder.  Does anyone know have a bit of code / custom action that does this ?

  • Seema

    8/13/2012 9:53:18 PM | Reply

    Grt post !!!!

    Can we refresh the page after download


  • Vamsi Kalam

    8/17/2012 7:39:27 AM | Reply

    Great post. This is exactly what we were looking for. I made the changes suggested above and it works like a charm. Have to test it on a FBA site though.


  • Mary Ottley

    8/21/2012 2:19:57 PM | Reply


    Could you post the updated ws file.  I see what needs to be done, but I don't have Visual Studio 2010, just 2008 and I can't open the solution.  So I cannot edit the cs file for the downloadzip.aspx.

  • Mary Ottley

    8/21/2012 3:10:58 PM | Reply


    Could you repost the wsp with the changes for the multiple site collections that Karen describes?  I don't have Visual Studio 2010 and I cannot open the project to edit, otherwise I would.  


  • Vamsi Kalam

    8/22/2012 7:38:30 PM | Reply

    This feature works great only at the site collection level but not at the site and subsite level. I have two seperate site collections http://servername and http://servername/sites/it. The above feature works only for document libraries that are at the site collection level. If I create a site/subsite under site collection for example http://servername/site1 or http://servername/sites/it/site1 and then create a document library under it, the feature doesn't work. Can you please advise on how to fix this issue


  • Shereen

    8/29/2012 11:34:13 AM | Reply

    Hey all, for those of you looking for Karen's fix. I'll have it ready and posted in the next day or two. Email me at shereen at qumsieh dot ca if you're interested in the solution, but i'll post here as well.

  • Sruthi

    9/6/2012 9:17:21 PM | Reply

    When I try this on our Windows Server 2008 Enterprise 64bit I am getting error in Download.aspx page as

    The type or namespace name ZipFileBuilder cannot be found.

    On this line:
    using (ZipFileBuilder builder = new ZipFileBuilder(ms))

    and also in ZipBuilder.cs page am getting error as
    Method must have a return type.

    On this line:
      public ZipFileBuilder(Stream outStream)
                zipStream = new ZipOutputStream(outStream);
                zipStream.SetLevel(9); //best compression

                factory = new ZipEntryFactory(DateTime.Now);

    What could be the issue here?
    I have placed the dll and also added SafeControl in Web.config.


  • taps

    9/25/2012 2:45:56 AM | Reply

    many thanks for this tutorial

    how do I download multiple files without zipping them

  • Phyrom

    9/25/2012 10:32:32 PM | Reply

    First of all it's a good solution which I'm looking for. All files that I need to zip are zipped and can be downloaded.

    The only problem I have is that I can't extract the zipped file as it say it containts the following errors.

    "Unknown Method in fileName"
    "No file to extract"

    I use WinRar. From the winrar when open that zip file I can see all file list inside the zip file but I can't extract.

    Please note that I have copied the 2 class files in my SharePoint project and take all functions from DownloadZip.aspx.cs.
    1. AddFile
    2. AddFolder
    3. WriteStreamToResponse
    4. Stuff in the Page_load, I've incorporated to button click event

    Also do note that there is no error at all during the process.

    Please advise.

  • John Owens

    11/8/2012 8:57:10 AM | Reply

    I'm trying this solution out and I'm getting a File Not Found everytime I hit the Download as a Zip button.   Anyone else have this issue?

  • thanby

    11/20/2012 11:35:30 AM | Reply

    I've got the same issue as John Owens, but it only happens on certain computers. There's no difference in the version of Windows or IE between them, but on some computers it works beautifully (thanks Bart, by the way) and others it gives a "File not found" error.

    I have tried using the solution in the post and I have tried it with the one posted by Goro in case they were different, but the results are identicle.

    I'm sure it's just a bug on my end but I can't figure out what. Does anyone have insight into this? What part of the process is broken to return that error?

  • Ivan

    1/30/2013 4:02:57 AM | Reply

    Thanks. I've used your ZipFileBuilder for my Item attchments.

  • Juan

    2/6/2013 6:59:40 AM | Reply

    I love this option and have started playing within the code.  So thanks for that!

    I am hoping someone can address two problems, however.
    1) the custom action button is showing up twice in subsites (http://portal/sitename/DocLib...).  How can this be resolved?
    2) The code errors out when using on subsite but works fine on top level site.  I tried multiple suggestions above and cannot get it to work.  Helping with this would mean a lot!

  • russell

    3/18/2013 8:41:22 AM | Reply

    Is there any way that we can limit this to only specific users. Maybe userw in a specific sharepoint group. I'm using this feature on our extranet, and my legal department wants to remove the ability to bulk download from specific users

  • russell

    3/18/2013 8:46:54 AM | Reply

    Any way to limit this to specific users, maybe members of a specific sharepoint group? I'm using the feature on our extranet, and my legal department wants to remove the ability for some specific users to download in bulk using this feature.

  • Christian

    3/29/2013 9:57:00 AM | Reply

    Does is also work on SharePoint 2013??

  • Wayne

    4/2/2013 5:35:48 AM | Reply

    Hi Bart,

    Thank you for a sorely needed solution.

    When I activated your WSP file in sharepoint I got the following error:  

    This solution contains invalid markup or elements that cannot be deployed as part of a sandboxed solution. Solution manifest for solution '0a0552de-c4d1-4807-9c78-cb0dee36eaf4' failed validation, file manifest.xml, line 7, character 4: The element 'Solution' in namespace ''; has invalid child element 'TemplateFiles' in namespace '';. List of possible elements expected: 'FeatureManifests, ActivationDependencies' in namespace '';.

    I saw a similar post from 2012 but did not see the answer.  Can you help?

  • Dave Stuart

    5/20/2013 7:57:44 PM | Reply

    Fantastic stuff! Keep on Bloggin!!

  • Ravi

    5/27/2013 2:40:57 AM | Reply

    Thanks for the article.
    I am stuck in middle, please suggest the way to proceed .
    I have followed the Steps mentioned by you the only difference is I m doing this in SandBoxed. If I select any of the file I m getting the newly added button in the Ribbon as File its giving me Error .
    Please suggest me where m i getting Wrong.


  • Syam

    8/7/2013 3:05:37 AM | Reply

    Hi Ravi,

    i have similar requirement to develop a sandbox solution for multiple files download.Is it possible to share me the sandbox solution code which you already did.


  • seshu

    8/11/2013 1:40:47 AM | Reply

    I have used the code downloaded from below link which fixed main issues
    >Download files from subsite
    >Download files from sites that does not have managed path

Pingbacks and trackbacks (8)+