Click here to Skip to main content
15,892,927 members
Articles / Database Development / SQL Server / SQL Server 2008

Zip Rendering Extension for SQL Server Reporting Services 2005/2008/2012

Rate me:
Please Sign up or sign in to vote.
5.00/5 (20 votes)
14 Aug 2013CPOL13 min read 205.1K   3.8K   39  
How to create and deploy a SSRS rendering extension, explained by a functional Zip Rendering extension for SSRS 2005, 2008 (R2) and 2012.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;

using Ionic.Zip;
using System.IO;
using System.Net;
using System.Xml;

using Microsoft.ReportingServices.OnDemandReportRendering;
using Microsoft.ReportingServices.Interfaces;

namespace ZipRenderer
{
    public class ZipRenderingProvider : IRenderingExtension
    {
        //These values will be loaded from rsreportserver.config in SetConfiguration
        private string configuration;

        private string description = "(unnamed rendering extension)";

        private List<SubRenderer> subRenderers;

        #region IRenderingExtension Members

        void IRenderingExtension.GetRenderingResource(CreateAndRegisterStream createAndRegisterStreamCallback, NameValueCollection deviceInfo)
        {
            //excelRenderer.GetRenderingResource(createAndRegisterStreamCallback, deviceInfo);
        }

        bool IRenderingExtension.Render(Microsoft.ReportingServices.OnDemandReportRendering.Report report, NameValueCollection reportServerParameters, NameValueCollection deviceInfo, NameValueCollection clientCapabilities, ref System.Collections.Hashtable renderProperties, CreateAndRegisterStream createAndRegisterStream)
        {
            // A buffer for copying the intermediateStream to the outputStream
            // http://stackoverflow.com/questions/230128/best-way-to-copy-between-two-stream-instances-c
            byte[] buffer = new byte[32768];

            Stream outputStream = createAndRegisterStream(report.Name, "zip", Encoding.UTF8, "application/zip", true, StreamOper.CreateAndRegister);
            using (MemoryStream outputMemoryStream = new MemoryStream())
            {
                //Create a Zip output and tell it to keep the provided stream open - we use it outside the using clause
                using (ZipOutputStream zipOutput = new ZipOutputStream(outputMemoryStream, true))
                {
                    //Read zip deviceinfo
                    try 
                    {
                        if (deviceInfo["CompressionLevel"] != null)
                            zipOutput.CompressionLevel = (Ionic.Zlib.CompressionLevel)Enum.Parse(typeof(Ionic.Zlib.CompressionLevel), deviceInfo["CompressionLevel"], true);
                        if (deviceInfo["CompressionMethod"] != null)
                            zipOutput.CompressionMethod = (Ionic.Zip.CompressionMethod)Enum.Parse(typeof(Ionic.Zip.CompressionMethod), deviceInfo["CompressionMethod"], true);
                        if (deviceInfo["Strategy"] != null)
                            zipOutput.Strategy = (Ionic.Zlib.CompressionStrategy)Enum.Parse(typeof(Ionic.Zlib.CompressionStrategy), deviceInfo["Strategy"], true);
                        if (deviceInfo["Comment"] != null)
                            zipOutput.Comment = deviceInfo["Comment"];
                        if (deviceInfo["EnableZip64"] != null)
                            zipOutput.EnableZip64 = (Ionic.Zip.Zip64Option)Enum.Parse(typeof(Ionic.Zip.Zip64Option), deviceInfo["EnableZip64"], true);
                        if (deviceInfo["IgnoreCase"] != null)
                            zipOutput.IgnoreCase = Boolean.Parse(deviceInfo["IgnoreCase"]);
                        if (deviceInfo["Encryption"] != null)
                            zipOutput.Encryption = (Ionic.Zip.EncryptionAlgorithm)Enum.Parse(typeof(Ionic.Zip.EncryptionAlgorithm), deviceInfo["Encryption"], true);
                        if (deviceInfo["Password"] != null)
                            zipOutput.Password = deviceInfo["Password"];
                    } 
                    catch (Exception e)
                    {
                        throw new System.Configuration.ConfigurationErrorsException("Invalid DeviceInfo configuration value", e);
                    }

                    foreach (SubRenderer sr in subRenderers)
                    {
                        if (sr.Render(report, reportServerParameters, deviceInfo, clientCapabilities, ref renderProperties))
                        {
                            zipOutput.PutNextEntry(report.Name + "." + sr.Extension);
                            // Do the actual copying.
                            // While the streams are copying, think of the
                            // possibilities: this is the place where you would
                            // be able to fire up that external PDF library
                            // and add the PDF background!
                            // But don't think too long, cause' computers are pretty fast
                            // these days and will probably have finished copying this
                            // stream by the time you even got to start reading.
                            while (true)
                            {
                                int read = sr.RegisteredStream.Read(buffer, 0, buffer.Length);
                                if (read <= 0)
                                {
                                    break;
                                }
                                zipOutput.Write(buffer, 0, read);
                            }

                            sr.CloseStreams();
                        }
                    }
                }

                //Write zip contents to the output
                outputMemoryStream.WriteTo(outputStream);
            }

            // Return false, because:
            // "A return value of true indicates that any properties added
            // to the report object model are saved into the intermediate format."
            // http://msdn.microsoft.com/en-us/library/microsoft.reportingservices.reportrendering.irenderingextension.render(SQL.90).aspx
            // ... and we're obviously not doing that, are we? Are we? ARE WE?

            // ^ this by Broes ^ - left in because it illustrates the shabby documentation of rendering extensions
            // properties added to the ..what.. saved into ..what??? and that's all you get. So just return false indeed.
            return false;
        }

        bool IRenderingExtension.RenderStream(string streamName, Microsoft.ReportingServices.OnDemandReportRendering.Report report, NameValueCollection reportServerParameters, NameValueCollection deviceInfo, NameValueCollection clientCapabilities, ref System.Collections.Hashtable renderProperties, CreateAndRegisterStream createAndRegisterStream)
        {
            return false;
        }

        #endregion

        #region IExtension Members

        string IExtension.LocalizedName
        {
            get { return description; }
        }

        /// <summary>
        /// Process XML data stored in the configuration file
        /// </summary>
        /// <param name="configuration">The XML string from the configuration file that contains extension configuration data.</param>
        void IExtension.SetConfiguration(string configuration)
        {
            this.configuration = configuration;

            // Create the document and load the Configuration element    
            XmlDocument doc = new XmlDocument();

            try
            {
                doc.LoadXml(configuration);

                //Check for the DeviceInfo element
                if (doc.DocumentElement.Name == "DeviceInfo")
                {
                    //Find the ZipRenderer node
                    XmlNode zipRendererNode = doc.DocumentElement.SelectSingleNode("ZipRenderer");
                    if (zipRendererNode == null)
                        throw new System.Configuration.ConfigurationErrorsException("Missing ZipRenderer node in configuration", doc.DocumentElement);

                    //Read this extension's description
                    description = zipRendererNode.Attributes["description"].Value;

                    //Read all of the SubRenderers configured
                    subRenderers = new List<SubRenderer>();
                    foreach (XmlNode zippedReportNode in zipRendererNode.SelectNodes("SubRenderers/SubRenderer"))
                    {
                        try
                        {
                            //Try and create a SubRenderer
                            subRenderers.Add(SubRenderer.CreateSubRenderer(
                                zippedReportNode.Attributes["format"].Value,
                                zippedReportNode.Attributes["extension"].Value,
                                zippedReportNode.InnerXml));
                        }
                        catch (System.Configuration.ConfigurationErrorsException ex)
                        {
                            //An invalid SubRenderer format was used.
                            throw new System.Configuration.ConfigurationErrorsException(
                                String.Format("The SubReport format {0} could not be found", zippedReportNode.Attributes["format"].Value),
                                ex, zippedReportNode);

                            //Tried ReportViewer but fails in 2008 / 2012. The ZipRenderer and the ReportViewer renderer will keep waiting for each other. Deadlock.
                            //TODO: Replace by WebServiceSubRenderer.
                            //subRenderers.Add(new ReportViewerSubRenderer(zippedReportNode.Attributes["format"].Value) 
                            //{ 
                            //    Extension = zippedReportNode.Attributes["extension"].Value, 
                            //    Configuration = zippedReportNode.InnerXml
                            //});
                        }
                    }
                }
            }
            catch (XmlException ex)
            {
                throw new System.Configuration.ConfigurationErrorsException("Failed to read configuration data: " + ex.Message, ex);
            }
        }

        #endregion
    }

    internal class CreateAndRegisterStreamStream
    {
        #region Attributes and Properties

        string name;

        internal string Name
        {
            get { return name; }
            set { name = value; }
        }
        string extension;

        internal string Extension
        {
            get { return extension; }
            set { extension = value; }
        }
        Encoding encoding;

        internal Encoding Encoding
        {
            get { return encoding; }
            set { encoding = value; }
        }
        string mimeType;

        internal string MimeType
        {
            get { return mimeType; }
            set { mimeType = value; }
        }
        bool willSeek;

        internal bool WillSeek
        {
            get { return willSeek; }
            set { willSeek = value; }
        }
        StreamOper operation;

        internal StreamOper Operation
        {
            get { return operation; }
            set { operation = value; }
        }

        protected Stream stream;

        internal Stream Stream
        {
            get { return stream; }
            set { stream = value; }
        }

        #endregion

        internal CreateAndRegisterStreamStream(string name,
            string extension,
            Encoding encoding,
            string mimeType,
            bool willSeek,
            StreamOper operation,
            Stream stream)
        {
            this.name = name;
            this.encoding = encoding;
            this.extension = extension;
            this.mimeType = mimeType;
            this.operation = operation;
            this.willSeek = willSeek;

            this.stream = stream;
        }

        internal virtual void CloseStream()
        {
            stream.Close();
        }
    }

    internal class CreateAndRegisterStreamUnclosableMemoryStream : CreateAndRegisterStreamStream
    {
        internal CreateAndRegisterStreamUnclosableMemoryStream(string name,
            string extension,
            Encoding encoding,
            string mimeType,
            bool willSeek,
            StreamOper operation)
            : base(name, extension, encoding, mimeType, willSeek, operation, new UnclosableMemoryStream())
        { }

        override internal void CloseStream()
        {
            ((UnclosableMemoryStream)stream).AllowClose = true;
            stream.Close();
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Netherlands Netherlands
I am a database-oriented software developer .. or an application-oriented database engineer, depending on the situation.

In past 7 or so years I have created stuff ranging from Windows Services to WPF, from GDI+ to SSIS packages and SSRS extensions.

My goal is to improve life and work for others - which should be the ultimate goal of any good software project.
And my articles on Codeproject can hopefully make you do the same.

Comments and Discussions