Thursday, August 16, 2007

Revised Dynamic RDLC Generation

I've revised some of the RLDC generation code. The basic idea is to build rather generic RLDC file off a DataSet. You can then bind both the DataSet and the RDLC to a ReportViewer control and get your report. I generate the RDLC by transforming the DataSet XML schema into a RDLC file via and XSLT transform.

Let me say that you can greatly improve the XSLT. In a custom solution where I've implemented this, I have a Page Header with the report title, column widths that expand to fit the whole page and a Footer. But that solution is highly customized and not fit as a generic report generator. Just keep in mind that you may adjust the XSLT to fit your needs.

First the code for C# helper class...


using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;

namespace x
{
/// <summary>
/// Summary description for RdlcEngine
/// </summary>
public class RdlcEngine
{
public RdlcEngine()
{
//
// TODO: Add constructor logic here
//
}

#region bind control
public static void BindControl(Microsoft.Reporting.WebForms.ReportViewer rv, DataSet data, string name)
{
string virtualRldc = HRWebsite.RdlcEngine.BuildRDLC(data, name);
BindControl(rv, data, name, virtualRldc);
}

public static void BindControl(Microsoft.Reporting.WebForms.ReportViewer rv, DataSet data, string name, string virtualRldc)
{
rv.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;

Microsoft.Reporting.WebForms.ReportDataSource rds = new Microsoft.Reporting.WebForms.ReportDataSource();
rds.Name = name + "_Table";
rds.Value = data.Tables[0];

Microsoft.Reporting.WebForms.LocalReport r = rv.LocalReport;
r.ReportPath = virtualRldc;
r.DataSources.Add(rds);
}
#endregion

#region RDLC
/// <summary>
/// constructs a simple report RDLC file based on a DataSet
/// </summary>
/// <param name="data"></param>
/// <param name="name"></param>
/// <returns></returns>
public static string BuildRDLC(DataSet data, string name)
{
// establish some file names
string virtualXslt = "xslt/rdlc.xsl";
string virtualRdlc = "rdlc/" + name + ".rdlc";
string virtualSchema = "rdlc/" + name + ".schema";

// set the NAME on the DataSet
// this may or may not be necessary, but the RDLC and DataSet
// will both have the same name if this is done.
data.DataSetName = name;

// write the DataSet Schema to a file
// we should be passing a DataSet with only one DataTable
// the rdlc.xsl does not account for multiple DataTables
string physicalSchema = HttpContext.Current.Server.MapPath(virtualSchema);
data.WriteXmlSchema(physicalSchema);

// load the DataSet schema in a DOM
XmlDocument xmlDomSchema = new XmlDocument();
xmlDomSchema.Load(physicalSchema);

// append the NAME to the schema DOM
// this is so we can pick it up in the rdlc.xsl
// and use it
xmlDomSchema.DocumentElement.SetAttribute("Name", name + "_Table");

// transform the Schema Xml with rdlc.xsl
string physicalXslt = HttpContext.Current.Server.MapPath(virtualXslt);
string xml = HRWebsite.General.TransformXml(xmlDomSchema.OuterXml, physicalXslt);

// save off the resultng RDLC file
string physicalRdlc = HttpContext.Current.Server.MapPath(virtualRdlc);
XmlDocument xmlDomRdlc = new XmlDocument();
xmlDomRdlc.LoadXml(xml);
xmlDomRdlc.Save(physicalRdlc);

// return the virtual path of the RDLC file
// this is needed by the asp:ReportViewer
return virtualRdlc;
}
#endregion

#region Render
public static byte[] RenderReport(DataSet data, string name, string type)
{
Microsoft.Reporting.WebForms.ReportDataSource rds = new Microsoft.Reporting.WebForms.ReportDataSource();
rds.Name = name + "_Table";
rds.Value = data.Tables[0];

string virtualRdlc = BuildRDLC(data, name);
Microsoft.Reporting.WebForms.LocalReport lr = new Microsoft.Reporting.WebForms.LocalReport();
lr.ReportPath = HttpContext.Current.Server.MapPath(virtualRdlc);
lr.DataSources.Add(rds);

return RenderReport(lr, name, type);
}

public static byte[] RenderReport(Microsoft.Reporting.WebForms.LocalReport lr, string name, string type)
{
string extension = string.Empty;
string mimeType = string.Empty;
switch (type)
{
case "PDF":
extension = "pdf";
mimeType = "application/pdf";
break;
case "Excel":
extension = "xls";
mimeType = "application/vnd.excel";
break;
case "Image":
extension = "emf";
mimeType = "application/image";
break;
default:
throw new Exception("Unrecognized type: " + type + ". Type must be PDF, Excel or Image.");
}

//The DeviceInfo settings should be changed based on the reportType
//http://msdn2.microsoft.com/en-us/library/ms155397.aspx
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.Append("<DeviceInfo>");
sb.Append("<OutputFormat>");
sb.Append(type);
sb.Append("</OutputFormat>");
sb.Append("<PageWidth>11in</PageWidth>");
sb.Append("<PageHeight>8.5in</PageHeight>");
sb.Append("<MarginTop>1in</MarginTop>");
sb.Append("<MarginLeft>1in</MarginLeft>");
sb.Append("<MarginRight>1in</MarginRight>");
sb.Append("<MarginBottom>1in</MarginBottom>");
sb.Append("</DeviceInfo>");
string deviceInfo = sb.ToString();

string encoding;
Microsoft.Reporting.WebForms.Warning[] warnings;
string[] streams;
byte[] result;

//Render the report
result = lr.Render(
type,
deviceInfo,
out mimeType,
out encoding,
out extension,
out streams,
out warnings);

HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = mimeType;
HttpContext.Current.Response.AddHeader("content-disposition", "attachment; filename=" + name + "." + extension);
HttpContext.Current.Response.BinaryWrite(result);
HttpContext.Current.Response.End();

return result;
}
#endregion
}
}


Next, the XSLT....


<?xml version="1.0"?>
<!-- Stylesheet for creating ReportViewer RDLC documents -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition"
>

<xsl:variable name="mvarName" select="/xs:schema/@Name"/>
<xsl:variable name="mvarFontSize">8pt</xsl:variable>
<xsl:variable name="mvarFontWeight">500</xsl:variable>
<xsl:variable name="mvarFontWeightBold">700</xsl:variable>


<xsl:template match="/">
<xsl:apply-templates select="/xs:schema/xs:element/xs:complexType/xs:choice/xs:element/xs:complexType/xs:sequence">
</xsl:apply-templates>
</xsl:template>

<xsl:template match="xs:sequence">
<Report xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition">
<BottomMargin>1in</BottomMargin>
<RightMargin>1in</RightMargin>
<LeftMargin>1in</LeftMargin>
<TopMargin>1in</TopMargin>
<InteractiveHeight>11in</InteractiveHeight>
<InteractiveWidth>8.5in</InteractiveWidth>
<Width>6.5in</Width>
<Language>en-US</Language>
<rd:DrawGrid>true</rd:DrawGrid>
<rd:SnapToGrid>true</rd:SnapToGrid>
<rd:ReportID>7358b654-3ca3-44a0-8677-efe0a55c7c45</rd:ReportID>

<xsl:call-template name="BuildDataSource">
</xsl:call-template>

<xsl:call-template name="BuildDataSet">
</xsl:call-template>

<Body>
<Height>0.50in</Height>
<ReportItems>
<Table Name="table1">
<DataSetName><xsl:value-of select="$mvarName" /></DataSetName>
<Top>0.5in</Top>
<Height>0.50in</Height>
<Header>
<TableRows>
<TableRow>
<Height>0.25in</Height>
<TableCells>

<xsl:apply-templates select="xs:element" mode="HeaderTableCell">
</xsl:apply-templates>

</TableCells>
</TableRow>
</TableRows>
</Header>
<Details>
<TableRows>
<TableRow>
<Height>0.25in</Height>
<TableCells>

<xsl:apply-templates select="xs:element" mode="DetailTableCell">
</xsl:apply-templates>

</TableCells>
</TableRow>
</TableRows>
</Details>
<TableColumns>

<xsl:apply-templates select="xs:element" mode="TableColumn">
</xsl:apply-templates>

</TableColumns>
</Table>
</ReportItems>
</Body>
</Report>
</xsl:template>

<xsl:template name="BuildDataSource">
<DataSources>
<DataSource Name="DummyDataSource">
<ConnectionProperties>
<ConnectString/>
<DataProvider>SQL</DataProvider>
</ConnectionProperties>
<rd:DataSourceID>84635ff8-d177-4a25-9aa5-5a921652c79c</rd:DataSourceID>
</DataSource>
</DataSources>
</xsl:template>

<xsl:template name="BuildDataSet">
<DataSets>
<DataSet Name="{$mvarName}">
<Query>
<rd:UseGenericDesigner>true</rd:UseGenericDesigner>
<CommandText/>
<DataSourceName>DummyDataSource</DataSourceName>
</Query>
<Fields>

<xsl:apply-templates select="xs:element" mode="Field">
</xsl:apply-templates>

</Fields>
</DataSet>
</DataSets>
</xsl:template>

<xsl:template match="xs:element" mode="Field">
<xsl:variable name="varFieldName">
<xsl:value-of select="@name" />
</xsl:variable>

<xsl:variable name="varDataType">
<xsl:choose>
<xsl:when test="@type='xs:int'">System.Int32</xsl:when>
<xsl:when test="@type='xs:string'">System.String</xsl:when>
<xsl:when test="@type='xs:dateTime'">System.DateTime</xsl:when>
<xsl:when test="@type='xs:boolean'">System.Boolean</xsl:when>
</xsl:choose>
</xsl:variable>

<Field Name="{$varFieldName}">
<rd:TypeName><xsl:value-of select="$varDataType"/></rd:TypeName>
<DataField><xsl:value-of select="$varFieldName"/></DataField>
</Field>
</xsl:template>

<xsl:template match="xs:element" mode="HeaderTableCell">
<xsl:variable name="varFieldName">
<xsl:value-of select="@name" />
</xsl:variable>

<TableCell>
<ReportItems>
<Textbox Name="textbox{position()}">
<rd:DefaultName>textbox<xsl:value-of select="position()"/>
</rd:DefaultName>
<Value><xsl:value-of select="$varFieldName"/></Value>
<CanGrow>true</CanGrow>
<ZIndex>7</ZIndex>
<Style>
<TextAlign>Center</TextAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingBottom>2pt</PaddingBottom>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<FontSize><xsl:value-of select="$mvarFontSize"/></FontSize>
<FontWeight><xsl:value-of select="$mvarFontWeightBold"/></FontWeight>
<BackgroundColor>#000000</BackgroundColor>
<Color>#ffffff</Color>
<BorderColor>
<Default>#ffffff</Default>
</BorderColor>
<BorderStyle>
<Default>Solid</Default>
</BorderStyle>
</Style>
</Textbox>
</ReportItems>
</TableCell>
</xsl:template>

<xsl:template match="xs:element" mode="DetailTableCell">
<xsl:variable name="varFieldName">
<xsl:value-of select="@name" />
</xsl:variable>

<TableCell>
<ReportItems>
<Textbox Name="{$varFieldName}">
<rd:DefaultName><xsl:value-of select="$varFieldName"/></rd:DefaultName>
<Value>=Fields!<xsl:value-of select="$varFieldName"/>.Value</Value>
<CanGrow>true</CanGrow>
<ZIndex>7</ZIndex>
<Style>
<TextAlign>Left</TextAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingBottom>2pt</PaddingBottom>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<FontSize><xsl:value-of select="$mvarFontSize"/></FontSize>
<FontWeight><xsl:value-of select="$mvarFontWeight"/></FontWeight>
<BackgroundColor>#e0e0e0</BackgroundColor>
<Color>#000000</Color>
<BorderColor>
<Default>#ffffff</Default>
</BorderColor>
<BorderStyle>
<Default>Solid</Default>
</BorderStyle>
</Style>
</Textbox>
</ReportItems>
</TableCell>
</xsl:template>

<xsl:template match="xs:element" mode="TableColumn">
<TableColumn>
<Width>0.75in</Width>
</TableColumn>
</xsl:template>

<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($text, $from)">
<xsl:variable name="before" select="substring-before($text, $from)"/>
<xsl:variable name="after" select="substring-after($text, $from)"/>
<xsl:variable name="prefix" select="concat($before, $to)"/>
<xsl:value-of select="$before"/>
<xsl:value-of select="$to"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$after"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Tuesday, August 7, 2007

Generate a RDLC dynamically for the VS 2005 Report Viewer Control

The main drawback to the Report Viewer control in VS 2005 is that it relies on a pre-defined xml definition for the Report, or RDLC file. In order to construct this file, you need to have you data on hand. This makes it difficult to use the Report Viewer control with any kind of Ad-Hoc report utility. You may still want to use the Report Viewer because it allows exporting to PDF or Excel.

I had just such a problem. I wanted the ability to run several queries / SPs against my database and return the results in the Report Viewer. But, I wanted the ability to add or edit any one of my reports on the fly. Doing so would require a custom RDLC file.

So, what did is I wrote an XSLT transform to transform the Schema XML of a DataSet into an RDLC. Now the XLST is very simple. It just takes a DataSet with a single DataTable and generates an RDLC in a 2 dimensional table.

You can extend the XSLT to do more complex things. The XSLT becomes a template for your reports.

Anyway, here is the code to bind the RDLC and the DataSet to the Report Viewer control, the XLST, and my Transform function. Please comment if you find this code useful or have any suggestions...



private void BindReportViewer(DataSet data)
{
string rdlcName = "MyReport";
string virtualRdlc = BuildRDLC(data, rdlcName);

ReportViewer1.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;

Microsoft.Reporting.WebForms.ReportDataSource rds = new Microsoft.Reporting.WebForms.ReportDataSource();
rds.Name = rdlcName + "_Table";
rds.Value = data.Tables[0];

Microsoft.Reporting.WebForms.LocalReport lr = ReportViewer1.LocalReport;
lr.ReportPath = virtualRdlc;
lr.DataSources.Add(rds);
}


/// <summary>
/// constructs a simple report RDLC file based on a DataSet
/// </summary>
/// <param name="data"></param>
/// <param name="name"></param>
/// <returns></returns>
private string BuildRDLC(DataSet data, string name)
{
// establish some file names
string virtualXslt = "xslt/rdlc.xsl";
string virtualRdlc = "rdlc/" + name + ".rdlc";
string virtualSchema = "rdlc/" + name + ".schema";

// set the NAME on the DataSet
// this may or may not be necessary, but the RDLC and DataSet
// will both have the same name if this is done.
data.DataSetName = name;

// write the DataSet Schema to a file
// we should be passing a DataSet with only one DataTable
// the rdlc.xsl does not account for multiple DataTables
string physicalSchema = Server.MapPath(virtualSchema);
data.WriteXmlSchema(physicalSchema);

// load the DataSet schema in a DOM
System.Xml.XmlDocument xmlDomSchema = new System.Xml.XmlDocument();
xmlDomSchema.Load(physicalSchema);

// append the NAME to the schema DOM
// this is so we can pick it up in the rdlc.xsl
// and use it
xmlDomSchema.DocumentElement.SetAttribute("Name", name + "_Table");

// transform the Schema Xml with rdlc.xsl
string physicalXslt = Server.MapPath(virtualXslt);
string xml = TransformXml(xmlDomSchema.OuterXml, physicalXslt);

// save off the resultng RDLC file
string physicalRdlc = Server.MapPath(virtualRdlc);
XmlDocument xmlDomRdlc = new XmlDocument();
xmlDomRdlc.LoadXml(xml);
xmlDomRdlc.Save(physicalRdlc);

// return the virtual path of the RDLC file
// this is needed by the asp:ReportViewer
return virtualRdlc;
}

public static string TransformXml(string xml, string xslFile)
{
string result = string.Empty;
using (System.IO.MemoryStream memory = new System.IO.MemoryStream(System.Text.Encoding.ASCII.GetBytes(xml)))
{
System.Xml.Xsl.XslCompiledTransform transform = new System.Xml.Xsl.XslCompiledTransform();
transform.Load(xslFile);

System.Xml.XPath.XPathDocument xpathDoc = new System.Xml.XPath.XPathDocument(memory);
System.Text.StringBuilder sb = new System.Text.StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
transform.Transform(xpathDoc, null, sw);

result = sb.ToString();
}
return result;
}
}


<?xml version="1.0"?>
<!-- Stylesheet for creating ReportViewer RDLC documents -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition"
>

<xsl:variable name="mvarName" select="/xs:schema/@Name"/>
<xsl:variable name="mvarFontSize">8pt</xsl:variable>
<xsl:variable name="mvarFontWeight">500</xsl:variable>
<xsl:variable name="mvarFontWeightBold">700</xsl:variable>


<xsl:template match="/">
<xsl:apply-templates select="/xs:schema/xs:element/xs:complexType/xs:choice/xs:element/xs:complexType/xs:sequence">
</xsl:apply-templates>
</xsl:template>

<xsl:template match="xs:sequence">
<Report xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner" xmlns="http://schemas.microsoft.com/sqlserver/reporting/2005/01/reportdefinition">
<BottomMargin>1in</BottomMargin>
<RightMargin>1in</RightMargin>
<LeftMargin>1in</LeftMargin>
<TopMargin>1in</TopMargin>
<InteractiveHeight>11in</InteractiveHeight>
<InteractiveWidth>8.5in</InteractiveWidth>
<Width>6.5in</Width>
<Language>en-US</Language>
<rd:DrawGrid>true</rd:DrawGrid>
<rd:SnapToGrid>true</rd:SnapToGrid>
<rd:ReportID>7358b654-3ca3-44a0-8677-efe0a55c7c45</rd:ReportID>

<xsl:call-template name="BuildDataSource">
</xsl:call-template>

<xsl:call-template name="BuildDataSet">
</xsl:call-template>

<Body>
<Height>0.50in</Height>
<ReportItems>
<Table Name="table1">
<DataSetName><xsl:value-of select="$mvarName" /></DataSetName>
<Top>0.5in</Top>
<Height>0.50in</Height>
<Header>
<TableRows>
<TableRow>
<Height>0.25in</Height>
<TableCells>

<xsl:apply-templates select="xs:element" mode="HeaderTableCell">
</xsl:apply-templates>

</TableCells>
</TableRow>
</TableRows>
</Header>
<Details>
<TableRows>
<TableRow>
<Height>0.25in</Height>
<TableCells>

<xsl:apply-templates select="xs:element" mode="DetailTableCell">
</xsl:apply-templates>

</TableCells>
</TableRow>
</TableRows>
</Details>
<TableColumns>

<xsl:apply-templates select="xs:element" mode="TableColumn">
</xsl:apply-templates>

</TableColumns>
</Table>
</ReportItems>
</Body>
</Report>
</xsl:template>

<xsl:template name="BuildDataSource">
<DataSources>
<DataSource Name="DummyDataSource">
<ConnectionProperties>
<ConnectString/>
<DataProvider>SQL</DataProvider>
</ConnectionProperties>
<rd:DataSourceID>84635ff8-d177-4a25-9aa5-5a921652c79c</rd:DataSourceID>
</DataSource>
</DataSources>
</xsl:template>

<xsl:template name="BuildDataSet">
<DataSets>
<DataSet Name="{$mvarName}">
<Query>
<rd:UseGenericDesigner>true</rd:UseGenericDesigner>
<CommandText/>
<DataSourceName>DummyDataSource</DataSourceName>
</Query>
<Fields>

<xsl:apply-templates select="xs:element" mode="Field">
</xsl:apply-templates>

</Fields>
</DataSet>
</DataSets>
</xsl:template>

<xsl:template match="xs:element" mode="Field">
<xsl:variable name="varFieldName">
<xsl:value-of select="@name" />
</xsl:variable>

<xsl:variable name="varDataType">
<xsl:choose>
<xsl:when test="@type='xs:int'">System.Int32</xsl:when>
<xsl:when test="@type='xs:string'">System.String</xsl:when>
<xsl:when test="@type='xs:dateTime'">System.DateTime</xsl:when>
<xsl:when test="@type='xs:boolean'">System.Boolean</xsl:when>
</xsl:choose>
</xsl:variable>

<Field Name="{$varFieldName}">
<rd:TypeName><xsl:value-of select="$varDataType"/></rd:TypeName>
<DataField><xsl:value-of select="$varFieldName"/></DataField>
</Field>
</xsl:template>

<xsl:template match="xs:element" mode="HeaderTableCell">
<xsl:variable name="varFieldName">
<xsl:value-of select="@name" />
</xsl:variable>

<TableCell>
<ReportItems>
<Textbox Name="textbox{position()}">
<rd:DefaultName>textbox<xsl:value-of select="position()"/>
</rd:DefaultName>
<Value><xsl:value-of select="$varFieldName"/></Value>
<CanGrow>true</CanGrow>
<ZIndex>7</ZIndex>
<Style>
<TextAlign>Center</TextAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingBottom>2pt</PaddingBottom>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<FontSize><xsl:value-of select="$mvarFontSize"/></FontSize>
<FontWeight><xsl:value-of select="$mvarFontWeightBold"/></FontWeight>
<BackgroundColor>#000000</BackgroundColor>
<Color>#ffffff</Color>
<BorderColor>
<Default>#ffffff</Default>
</BorderColor>
<BorderStyle>
<Default>Solid</Default>
</BorderStyle>
</Style>
</Textbox>
</ReportItems>
</TableCell>
</xsl:template>

<xsl:template match="xs:element" mode="DetailTableCell">
<xsl:variable name="varFieldName">
<xsl:value-of select="@name" />
</xsl:variable>

<TableCell>
<ReportItems>
<Textbox Name="{$varFieldName}">
<rd:DefaultName><xsl:value-of select="$varFieldName"/></rd:DefaultName>
<Value>=Fields!<xsl:value-of select="$varFieldName"/>.Value</Value>
<CanGrow>true</CanGrow>
<ZIndex>7</ZIndex>
<Style>
<TextAlign>Left</TextAlign>
<PaddingLeft>2pt</PaddingLeft>
<PaddingBottom>2pt</PaddingBottom>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<FontSize><xsl:value-of select="$mvarFontSize"/></FontSize>
<FontWeight><xsl:value-of select="$mvarFontWeight"/></FontWeight>
<BackgroundColor>#e0e0e0</BackgroundColor>
<Color>#000000</Color>
<BorderColor>
<Default>#ffffff</Default>
</BorderColor>
<BorderStyle>
<Default>Solid</Default>
</BorderStyle>
</Style>
</Textbox>
</ReportItems>
</TableCell>
</xsl:template>

<xsl:template match="xs:element" mode="TableColumn">
<TableColumn>
<Width>0.75in</Width>
</TableColumn>
</xsl:template>

<xsl:template name="replace-string">
<xsl:param name="text"/>
<xsl:param name="from"/>
<xsl:param name="to"/>
<xsl:choose>
<xsl:when test="contains($text, $from)">
<xsl:variable name="before" select="substring-before($text, $from)"/>
<xsl:variable name="after" select="substring-after($text, $from)"/>
<xsl:variable name="prefix" select="concat($before, $to)"/>
<xsl:value-of select="$before"/>
<xsl:value-of select="$to"/>
<xsl:call-template name="replace-string">
<xsl:with-param name="text" select="$after"/>
<xsl:with-param name="from" select="$from"/>
<xsl:with-param name="to" select="$to"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Wednesday, March 21, 2007

C# Zip Utility using the vjslib.dll

At some time or another, everybody needs to work with zip files. Unfortunately C# has no native zip libraries. You can use third party components or even the free CSharpZip library. But what if (a) you don't want to pay for a third party component and (b) you'd prefer a simple, non-compiled solution the you can just add to single page?

The J# libraries are available and they do contain classes for the handling of zip files. These classes are contained within the vjslib.dll. All we need to do is create wrappers for the ZipOutputStream, ZipInputStream and ZipEntry. We can easily create these wrappers using system.reflection.

The answer is the single aspx file below. There is a zip class defined with three main methods, create, append and extract. These methods use the wrappers for the J# classes and allow you to manipulate the zip file.



<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Runtime.InteropServices" %>
<%@ Import Namespace="System.Reflection" %>
<%@ Import Namespace="System.Collections" %>
<%@ Import Namespace="System.IO" %>


<%@ Page language="C#" Debug="true" %>
<script runat="server">
/*
* ********* ********* ********* ********* *********
* REFERENCES
* http://www.codeproject.com/csharp/VmEasyZipUnZip.asp
* http://www.samspublishing.com/articles/article.asp?p=27219&seqNum=8&rl=1
* http://www.codeguru.com/forum/showthread.php?t=362530
* ********* ********* ********* ********* *********
*/
/*
* ********* ********* ********* ********* *********
* main class for handling zip files
* ********* ********* ********* ********* *********
*/
public class cZip
{
/// <summary>
/// Gets the name of the assembly from the physical path to vjslib.dll
/// </summary>
private string GetAssemblyName()
{
string path = "C:\\WINDOWS\\Microsoft.NET\\Framework\\v2.0.50727\\vjslib.dll";
Assembly a = Assembly.LoadFile(path);
return a.ToString();
}

/// <summary>
/// Extracts the contents of a zip file to the specified path, returns the list of files extracted
/// </summary>
/// <param name="virtualZip">virtual path to the zip file</param>
/// <param name="virtualPath">virtual path to extract the files to</param>
public string Extract(string virtualZip, string virtualPath)
{
StringBuilder sb = new StringBuilder();

// translate virtual paths to physical paths
string physicalZip = HttpContext.Current.Server.MapPath(virtualZip);
string physicalPath = HttpContext.Current.Server.MapPath(virtualPath);

// create objects from the java wrappers
// (wrapper source code is below)
string assemblyName = GetAssemblyName();
ZipInputStream zis = new ZipInputStream(assemblyName, physicalZip);

string title = zis.GetNextEntry();
while (title.Length != 0)
{
// append file to list
if (sb.ToString().Length > 0)
sb.Append(",");
sb.Append(virtualPath + "/" + title);

// create a BinaryWriter to write data
string file = physicalPath + "\\" + title;
System.IO.FileStream fs = System.IO.File.Open(file, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write);
System.IO.BinaryWriter br = new System.IO.BinaryWriter(fs);

sbyte[] sbuffer = new sbyte[1024];
int len = zis.Read(sbuffer);
while (len > 0)
{
byte[] buffer = ConvertToByte(sbuffer);
br.Write(buffer, 0, len);
len = zis.Read(sbuffer);
}
br.Close();
title = zis.GetNextEntry();
}
zis.Close();
return sb.ToString();
}

/// <summary>
/// Creates a zip file from a comma seperated list of virtual filenames
/// </summary>
/// <param name="virtualZip">virtual path to the zip file</param>
/// <param name="virtualFileList">comma seperated list of virtual filenames</param>
public void Create(string virtualZip, string virtualFileList)
{
// translate virtual paths to physical paths
string physicalZip = HttpContext.Current.Server.MapPath(virtualZip);

// create objects from the java wrappers
// (wrapper source code is below)
string assemblyName = GetAssemblyName();
ZipOutputStream zos = new ZipOutputStream(assemblyName, physicalZip);

string[] list = virtualFileList.Split(",".ToCharArray());
for (int i=0; i<list.Length; i++)
{
string physicalFile = HttpContext.Current.Server.MapPath(list[i]);
string title = physicalFile.Substring(physicalFile.LastIndexOf("\\") +1);

// create the Zip Entry object
ZipEntry ze = new ZipEntry(assemblyName, title);
zos.PutNextEntry(ze);

// create a BinaryReader to read data from the file that we want to add
System.IO.FileStream fs = System.IO.File.Open(physicalFile, System.IO.FileMode.Open, System.IO.FileAccess.Read);
System.IO.BinaryReader br = new System.IO.BinaryReader(fs);

// add the file to the zip, by reading the file through
// the standard C# Binary Reader and write it to the java
// ZipOutputStream
byte[] buffer = new byte[1024];
int len = br.Read(buffer, 0, 1024);

while (len>0)
{
sbyte[] sbuffer = ConvertToSByte(buffer);
zos.Write(sbuffer, 0, len);
len = br.Read(buffer, 0, 1024);
}
br.Close();
}
zos.Close();
}

/// <summary>
/// Adds one or more files to an existing zip.
/// </summary>
/// <param name="virtualZip">virtual path to the zip file</param>
/// <param name="virtualFileList">comma seperated list of virtual filenames</param>
public void Append(string virtualZip, string virtualFileList)
{
string physicalZip = HttpContext.Current.Server.MapPath(virtualZip);
string tempDir = "ZipTemp";
string list = string.Empty;
Boolean zipExists = false;

FileInfo fi = new FileInfo(physicalZip);
if (fi != null)
{
if (fi.Exists)
zipExists = true;
}

if (zipExists)
{
// to append files, we must first extract the existing files to a folder
// because every time a ZipOutputStream is created it overwrites an existing file
// so we extract to a list, append the new files, and recreate the zip
CreateFolder(tempDir);
list = Extract(virtualZip, "/" + tempDir);
if (list.Length != 0)
list += ",";
list += virtualFileList;
Create(virtualZip, list);
DeleteFolder(HttpContext.Current.Server.MapPath("/" + tempDir));
}
else
{
list = virtualFileList;
Create(virtualZip, list);
}
}

private sbyte[] ConvertToSByte(byte[] src)
{
sbyte[] dst = new sbyte[src.Length];
System.Buffer.BlockCopy(src, 0, dst, 0, src.Length);
return dst;
}
private byte[] ConvertToByte(sbyte[] src)
{
byte[] dst = new byte[src.Length];
System.Buffer.BlockCopy(src, 0, dst, 0, src.Length);
return dst;
}
private void CreateFolder(string name)
{
string physicalPath = HttpContext.Current.Server.MapPath("/");
DirectoryInfo folder = new DirectoryInfo(physicalPath);
folder.CreateSubdirectory(name);
}
private void DeleteFolder(string physicalPath)
{
DirectoryInfo folder = new DirectoryInfo(physicalPath);
foreach (DirectoryInfo subfolder in folder.GetDirectories())
DeleteFolder(subfolder.FullName);
foreach (FileInfo file in folder.GetFiles("*.*"))
{
file.Attributes = (FileAttributes.Normal);
file.Delete();
}
folder.Delete();
}
private string ToVirtualPath(string physicalPath)
{
string root = HttpContext.Current.Server.MapPath("/");
if (physicalPath.IndexOf(root) == -1)
return string.Empty;

string virtualPath = physicalPath.Replace(root, "/");
virtualPath = virtualPath.Replace("\\", "/");
return virtualPath;
}
public void ShowMethods(string className)
{
// create an instance of the object
string assemblyName = GetAssemblyName();
Assembly a = Assembly.Load(assemblyName);
Type t = a.GetType(className);

foreach(MethodInfo mi in t.GetMethods())
{
DbgWrite(mi.Name);
foreach (ParameterInfo pi in mi.GetParameters())
DbgWrite(pi.ParameterType.ToString());
}
foreach(PropertyInfo pi in t.GetProperties())
DbgWrite(pi.Name);
}
public void DbgWrite(string data)
{
HttpContext.Current.Response.Write("<div style='background-color:#cccccc'>");
HttpContext.Current.Response.Write(data);
HttpContext.Current.Response.Write("</div>");
}
}

/*
* ********* ********* ********* ********* *********
* wrapper for the java.util.zip.ZipOutputStream (late binds to object in vjslib.dll)
* NOTE: This wrapper includes the java.io.FileOutputStream which is required
* to contruct the ZipOutputStream
* ********* ********* ********* ********* *********
*/
public class ZipOutputStream : IDisposable
{
Assembly _assem;
Type _type;
object _inst;

Type _typeFileStream;
object _fileStream;

public ZipOutputStream(string assemblyName, string file)
{
_assem = Assembly.Load(assemblyName);

// create an instance of java.io.FileOutputStream
_typeFileStream = _assem.GetType("java.io.FileOutputStream");
object[] args = new object[] { file };
_fileStream = Activator.CreateInstance(_typeFileStream, args);

// create an instance of the java.util.zip.ZipOutputStream
_type = _assem.GetType("java.util.zip.ZipOutputStream");
args = new object[] { _fileStream };
_inst = Activator.CreateInstance(_type, args);
}
public void Dispose()
{
MethodInfo mi = _type.GetMethod("dispose");
if (mi != null)
mi.Invoke(_inst, null);
}
public void CloseEntry()
{
MethodInfo mi = _type.GetMethod("closeEntry");
mi.Invoke(_inst, null);
}
public void Close()
{
MethodInfo mi = _type.GetMethod("close");
mi.Invoke(_inst, null);

mi = _typeFileStream.GetMethod("close");
mi.Invoke(_fileStream, null);
}
public void PutNextEntry(ZipEntry ze)
{
MethodInfo mi = _type.GetMethod("putNextEntry");
object[] args = new object[] { ze.GetInstance() };
mi.Invoke(_inst, args);
}
public void Write(sbyte[] buffer, int start, int len)
{
object[] args = new object[3];
args[0] = buffer;
args[1] = 0;
args[2] = len;

Type[] types = new Type[3] { typeof(sbyte[]), typeof(int), typeof(int) }; // identify the correct Method by #arguments
BindingFlags b = BindingFlags.Public BindingFlags.Instance; // public or contructor method
MethodInfo mi = _type.GetMethod("write", b, null, types, null);
mi.Invoke(_inst, args);
}
}

/*
* ********* ********* ********* ********* *********
* wrapper for the java.util.zip.ZipInputStream (late binds to object in vjslib.dll)
* NOTE: This wrapper includes the java.io.FileInputStream which is required
* to contruct the ZipInputStream
* ********* ********* ********* ********* *********
*/
public class ZipInputStream : IDisposable
{
Assembly _assem;
Type _type;
object _inst;

Type _typeFileStream;
object _fileStream;

public ZipInputStream(string assemblyName, string file)
{
_assem = Assembly.Load(assemblyName);

// create an instance of java.io.FileInputStream
_typeFileStream = _assem.GetType("java.io.FileInputStream");
object[] args = new object[] { file };
_fileStream = Activator.CreateInstance(_typeFileStream, args);

// create an instance of the java.util.zip.ZipOutputStream
_type = _assem.GetType("java.util.zip.ZipInputStream");
args = new object[] { _fileStream };
_inst = Activator.CreateInstance(_type, args);
}
public void Dispose()
{
MethodInfo mi = _type.GetMethod("dispose");
if (mi != null)
mi.Invoke(_inst, null);
}
public void CloseEntry()
{
MethodInfo mi = _type.GetMethod("closeEntry");
mi.Invoke(_inst, null);
}
public void Close()
{
MethodInfo mi = _type.GetMethod("close");
mi.Invoke(_inst, null);

mi = _typeFileStream.GetMethod("close");
mi.Invoke(_fileStream, null);
}
public string GetNextEntry()
{
MethodInfo mi = _type.GetMethod("getNextEntry");
object ze = mi.Invoke(_inst, null);
if (ze != null)
{
Type typeZe = ze.GetType();
mi = typeZe.GetMethod("getName");
object result = mi.Invoke(ze, null);
return result.ToString();
}
return string.Empty;
}
public int Read(sbyte[] buffer)
{
object[] args = new object[1];
args[0] = buffer;

Type[] types = new Type[1] { typeof(sbyte[]) }; // identify the correct Method by #arguments
BindingFlags b = BindingFlags.Public BindingFlags.Instance; // public or contructor method
MethodInfo mi = _type.GetMethod("read", b, null, types, null);
object result = mi.Invoke(_inst, args);
return int.Parse(result.ToString());
}
}


/*
* ********* ********* ********* ********* *********
* wrapper for the java.util.zip.ZipEntry (late binds to object in vjslib.dll)
* ********* ********* ********* ********* *********
*/
public class ZipEntry : IDisposable
{
Assembly _assem;
Type _type;
object _inst;

public ZipEntry(string assemblyName, string data)
{
_assem = Assembly.Load(assemblyName);
_type = _assem.GetType("java.util.zip.ZipEntry");

object[] args = new object[] { data };
_inst = Activator.CreateInstance(_type, args);
}
//public ZipEntry(Assembly a, object zeInstance)
//{
// _assem = a;
// _type = _assem.GetType("java.util.zip.ZipEntry");
// _inst = zeInstance;
//}
public void Dispose()
{
MethodInfo mi = _type.GetMethod("dispose");
if (mi != null)
mi.Invoke(_inst, null);
}
public string GetName()
{
MethodInfo mi = _type.GetMethod("getName");
object result = mi.Invoke(_inst, null);
return result.ToString();
}
public object GetInstance()
{
return _inst;
}
}


/*
* ********* ********* ********* ********* *********
* PAGE LOAD
* ********* ********* ********* ********* *********
*/
private void Page_Load(object sender, System.EventArgs e)
{
cZip z = new cZip();
//z.ShowMethods("java.util.zip.ZipEntry");
z.Create("/zip.zip", "/zip.aspx,/win2000.gif,winxp.gif");
z.Append("/zip.zip", "/warning.gif,/help.gif");


if (!IsPostBack)
{
// not a re-post
}
else
{
// re-post
}
}
</script>


<html>
<head>
</head>

<body>
<form runat="server">

</form>
</body>
</html>


grow taller