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();
}
}
}