SharePoint Designer Zip/Compress Workflow Action Deployed in a Sandboxed Solution
31. May 2011 07:41

Whew, that was a long title – But I think you’ll find this very useful.  Some notes on deploying a custom workflow action that you can use inside of SharePoint designer, which will Zip attachments to list items and deposit the compressed file in a document library – and it is all deployed in a sandboxed solution, so you can use it on hosted SharePoint Foundation instances.  In this case, I even generate PDF files with the data from the list items, and include them in the compressed file.  Great for archival or offsite storage.

 

First of all, this MSDN article about general workflow actions and sandboxed solutions will help you immensely with the basics:

http://msdn.microsoft.com/en-us/library/gg615449.aspx

Next you’ll want to grab a copy of:

  • SharpZipLib (an open source compression library)
  • SharpPDF (if you want to generate PDF’s too)

You’ll want to get the source to each, as we’ll upgrade them to .net 4.0, and build them as signed assemblies.

 

Make yourself a SharePoint solution in Visual Studio per the MSDN article above, and add two (or one if you are leaving out the PDF bits) existing projects using the source of the libraries (SharpZip, SharpPDF). 

  • You’ll need to let the upgrade wizard upgrade them to .net 4.
  • Check the project settings on them and make sure they are targeting the .net 4 framework (AnyCPU)
  • For SharpPDF you’ll need to open the AssemblyInfo.cs file and add a line to the bottom
[assembly: AllowPartiallyTrustedCallers()]

 

In my case, I’m always looking at a single list, so my workflow action only accepts a ListItemID.  You’ll probably want to make that more generic by adding a few more parameters to your Action.  Again, these custom actions are described in plenty of detail in the MSDN article referenced above.

 

       public static Hashtable Package(SPUserCodeWorkflowContext context, string ItemID)
        {
            Hashtable result = new Hashtable();

            try
            {
                result["Result"] = PackageListItem(closingId, context.CurrentWebUrl);
            }
            catch (Exception e)
            {
                result["Result"] = "ERROR" + e.ToString();
            }

            return result;
        }

 

Where the PackageListItem method looks like this:

 

        public static string PackageListItem(string ItemID, string webUrl)
        {
            SortedList<string, byte[]> files = new SortedList<string,byte[]>();
            
            using (SPSite site = new SPSite(webUrl))
            {
                using (SPWeb web = site.OpenWeb())
                {
                    //Find the list item
                    SPListItem item = web.Lists["List"].GetItemById(int.Parse(ItemID));

                    //Create a PDF of the list item data and add it to the 
                    //  compressed files collection
                    files.Add("ListData.pdf", CreateDocument(item, "Title"));

                    //Loop through all list item attachments
                    //  and add them to the compressed file collection
                    foreach (string attachment in item.Attachments)
                    {
                        SPFile attFile = web.GetFile(item.Attachments.UrlPrefix + attachment);
                        files.Add(attachment, attFile.OpenBinary());
                    }
                    
                    //Zip everything up and finish!
                    byte[] package = CreateZip(files);

                    files = null;

                    web.AllowUnsafeUpdates = true;

                    //Add the zipped package to a doc lib
                    SPFile file = web.Folders["Archive"].Files.Add(
                        string.Format("({0})_{1}.zip", 
                        ItemID, 
                        DateTime.Now.ToString("yyyy'-'MM'-'dd HH''mm''ss")), 
                        package);

                    web.Update();

                    file.Update();

                    web.AllowUnsafeUpdates = false;

                    package = null;

                    //return the List Item ID of the generated file to the workflow
                    return file.ListItemAllFields["ID"].ToString();

                }
            }
        }

 

And create document, if you are interested in generating PDF’s, looks like this.  I do some hackery to leave out boring version and guid fields from the list data – feel free to do something more intelligent:

 

 

 public static byte[] CreateDocument(SPListItem item, string type)
        {
            pdfDocument pdf = new pdfDocument(type, "Some Text");
            pdfPage page = pdf.addPage();

            page.addText(item["Title"].ToString(), 20, 730, pdf.getFontReference(predefinedFont.csHelvetica), 12);

            pdfTable table = new pdfTable(pdf, 1, pdfColor.Black);
            table.borderSize = 1;
            table.borderColor = pdfColor.Black;
            table.coordX = 50;
            table.coordY = 690;


            table.tableHeader.addColumn(150);
            table.tableHeader.addColumn(300);

            foreach (SPField f in item.Fields)
            {
                if (f.Type != SPFieldType.Guid && !f.Title.ToLower().Contains("version") && !f.Title.Contains("Title"))
                {
                    try
                    {
                        pdfTableRow row = table.createRow();
                        row[0].addText(f.Title);
                        row[1].addText(item[f.InternalName].ToString());
                        table.addRow(row);
                    }
                    catch { }
                }
            }
            
            page.addTable(table);

            MemoryStream ms = new MemoryStream();
            pdf.createPDF(ms);

            return ms.ToArray();
        }

 

The CreateZip method called in package is also very simple:

 

 public static byte[] CreateZip(SortedList<string, byte[]> files)
        {

            MemoryStream ms = new MemoryStream();

            using (ZipOutputStream s = new ZipOutputStream(ms))
            {

                s.SetLevel(5); // 0 - store only to 9 - means best compression

                foreach (string file in files.Keys)
                {

                    // Using GetFileName makes the result compatible with XP
                    // as the resulting path is not absolute.
                    ZipEntry entry = new ZipEntry(file);

                    // Could also use the last write time or similar for the file.
                    entry.DateTime = DateTime.Now;
                    s.PutNextEntry(entry);

                    s.Write(files[file], 0, files[file].Length);                    
                }

            }

            return ms.ToArray();

        }

This is relatively resource intensive of course, and will use some of your sandboxed resources.  It hasn’t been a problem for me, but your mileage may vary.

Tags: Comments (0) | Permalink

Comments

Comments are closed