/*=====================================================================
File: ReportViewer.cs
Summary: Main class for the Microsoft SQL Server Reporting Services sample
server control.
---------------------------------------------------------------------
=====================================================================*/
#region Disclaimer by Teo Lachev "Microsoft Reporting Services in Action"
/*============================================================================
File: RsReportViewer.cs
Summary: An enhanced version of the ReportViewer sample control
--------------------------------------------------------------------
This code sample was built upon on the Microsoft's ReportViewer control included
with the RS samples.
The following portions of the code have been changed/added to fit the book needs:
1. Supports server-side report generation.
2. Supports ICustomTypeDescriptor
===========================================================================*/
#endregion
using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Specialized;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing.Design;
using System.Drawing;
// my namespaces
using RsReportViewer.RS;
using System.Text;
using System.Web;
using System.Web.Services.Protocols;
using System.IO;
using System.Diagnostics;
using System.Xml;
using System.Data;
using System.Net;
namespace RSControl.WebViewer
{
public class RSReportViewer : WebControl, ICustomTypeDescriptor
{
#region Private Member Variables
// Private members that will map to url access builder.
private multiState _showToolbar = 0;
private multiState _parameters = 0;
private string _zoom = "100%";
private string _searchstring = String.Empty;
private string _searchnextstring = String.Empty;
private string _reportParameters = String.Empty;
private string _addFilter = String.Empty;
private string _filterValues = String.Empty;
private string _serverUrl = "";
private string _url = String.Empty;
private String _reportPath = "";
private string _renderingFormat = "Default";
private Hashtable _properties = new Hashtable();
// my properties
private multiState _serverSide = 0; // client-side or server-side report generation
private HybridDictionary _parameterValues = new HybridDictionary(); // report request
private string _streamRoot = "http://localhost/RSTestApp/ImageHandler.aspx?ID=";
private string _imageDownloadPath =@"temp";
#endregion
#region Public Enums for Property Browser
public enum multiState {Default, True, False};
#endregion
#region Enum to String Conversion Arrays
private string[] multiStateArray = {null, "true", "false"};
#endregion
#region Constructors
public RSReportViewer()
{
}
#endregion
#region Propertes
[Browsable(true), ReadOnly(true),
Category("General Report Parameters"),
Description("Full report url.")]
public string Url
{
get
{
return this._url;
}
}
[Category("General Report Parameters"),
Editor(typeof(StringEditor), typeof(UITypeEditor)),
Description("Server url such as http://localhost/reportserver")]
public string ServerUrl
{
get
{
return this._serverUrl;
}
set
{
this._serverUrl = value;
// Build the full url string when the property is set.
this.BuildUrlString();
}
}
[Category("General Report Parameters"),
Description("Report path such as /SampleReports/Company Sales")]
public String ReportPath
{
get
{
return this._reportPath;
}
set
{
this._reportPath = value;
// Build the full url string when the property is set.
this.BuildUrlString();
}
}
[Category("HTMLViewer Commands"),
Description("Indicates whether to display the report toolbar.")]
public multiState ToolbarVisible
{
get
{
return this._showToolbar;
}
set
{
this._showToolbar = value;
this.SetParameter("rc:toolbar", multiStateArray[(int)value]);
}
}
[Category("HTMLViewer Commands"),
Description("Indicates whether to show the parameters area of the toolbar.")]
public multiState ParametersVisible
{
get
{
return this._parameters;
}
set
{
this._parameters = value;
this.SetParameter("rc:parameters", multiStateArray[(int)value]);
}
}
[Category("HTMLViewer Commands"),
Description("Sets the zoom property of the report"),
Editor(typeof(ZoomListboxTypeEditor), typeof(UITypeEditor))
]
public string Zoom
{
get
{
return this._zoom;
}
set
{
this._zoom = value;
this.SetParameter("rc:zoom", value);
}
}
[Category("Rendering Format"),
Editor(typeof(FormatListboxTypeEditor), typeof(UITypeEditor)),
Description("Sets the report rendering format.")
]
public string Format
{
get
{
return this._renderingFormat.ToLower()=="default"?"HTML4.0":this._renderingFormat;
}
set
{
this._renderingFormat = value;
this.SetParameter("rs:Format", value);
}
}
#endregion
#region My Properties
[Category("Server-side Execution Options"),
Description("Indicates whether to generate the report on the server-side.")]
public multiState ServerSide
{
get
{
return this._serverSide;
}
set
{
this._serverSide = value;
}
}
[Category("Server-side Execution Options"),
Description("The absolute URL to the server-side image handler page to render images with HTML reports. Corresponds to the StreamRoot device setting. If left blank, defaults to the ImageDownloadPath.")]
public string StreamRoot
{
get
{
return this._streamRoot;
}
set
{
this._streamRoot = value;
}
}
[Category("Server-side Execution Options"),
Description("The relative to the the application virtual root folder where the images will be saved for HTML reports.")]
public string ImageDownloadPath
{
get
{
return this._imageDownloadPath;
}
set
{
this._imageDownloadPath = value;
}
}
public void AddParameter (string name, string value)
{
_parameterValues.Add(name, value);
}
[Browsable(true), ReadOnly(true),
Category("Server-side Execution Options"),
Description("Set this to an instance of a .NET dataset during runtime for reports which use the Custom Dataset Extension. If you set this property, the ReportViewer will add a DataSource parameter for you in the Report Parameters collection.")]
public System.Data.DataSet DataSource
{
get
{
return null;
}
set
{
// if the dataset parameter is set during runtime
// add DataSource parameter to the parameter collection
if (_parameterValues["DataSource"]!=null) _parameterValues.Remove("DataSource");
StringBuilder stringBuilder = new StringBuilder();
StringWriter stringWriter = new StringWriter(stringBuilder);
value.WriteXml(stringWriter, XmlWriteMode.WriteSchema);
_parameterValues.Add("DataSource", stringBuilder.ToString());
stringWriter.Close();
}
}
/// <summary>
/// Determines whether the report is HTML-based
/// </summary>
/// <param name="reportFormat">The Report format</param>
/// <returns>true, if the report is requested in HTML</returns>
private bool IsHtmlReport
{
get
{
return ("ht" == this.Format.Substring(0, 2).ToLower());
}
}
#endregion
#region Methods
/// <summary>
/// Add or remove url access string properties.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
private void SetParameter(string name, string value)
{
try
{
// Remove if value is null or empty. Value is null of the property grid value
// is null or empty. Empty or null removes the property from the Hashtable.
if(value == null | value == String.Empty )
{
this._properties.Remove(name);
}
else
{
if(this._properties.ContainsKey(name))
{
// Change if key exists
this._properties[name] = value;
}
else
{
// Add if key does not exist
this._properties.Add(name, value);
}
}
// Build a new url string
this.BuildUrlString();
}
// Catch and handle a more specific exception in a propduction application.
catch(Exception ex)
{
// Sample throws the exception to the client
throw ex;
}
}
/// <summary>
/// Enumerate Hashtable and create report server access specific string.
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
private string EmumProperties(Hashtable properties)
{
string paramsString = String.Empty;
// Enumerate properties and create report server specific string.
IDictionaryEnumerator customPropEnumerator = properties.GetEnumerator();
while ( customPropEnumerator.MoveNext() )
{
paramsString += "&"
+ customPropEnumerator.Key
+ "=" + customPropEnumerator.Value;
}
return paramsString;
}
/// <summary>
/// Add URL access command for rendering a report and any
/// additional parameters.
/// </summary>
public string BuildUrlString()
{
this._url = this._serverUrl + "?" + this._reportPath +
"&rs:Command=Render" + this.EmumProperties(this._properties);
return this._url;
}
#endregion
#region Render
/// <summary>
/// Render the report on the client or server-side
/// </summary>
/// <param name="output"></param>
protected override void Render(HtmlTextWriter output)
{
if (this._serverUrl == String.Empty || this._reportPath == String.Empty)
{
output.Write(MessageToHTML("To render a report, enter the ServerUrl and ReportPath."));
return;
}
if (this.ServerSide == multiState.True && System.Web.HttpContext.Current == null)
{
// running in design mode
return;
}
if (this.ServerSide != multiState.True)
{
// Client-side generation is the same as with the Report Viewer control
// Create IFrame if the user enters ServerUrl and ReportPath
output.WriteBeginTag("iframe");
output.WriteAttribute("src", this.BuildUrlString());
output.WriteAttribute("width", this.Width.ToString());
output.WriteAttribute("height", this.Height.ToString());
output.WriteAttribute("style", "border: 1 solid #C0C0C0");
output.WriteAttribute("border", "0");
output.WriteAttribute("frameborder", "0");
output.Write(HtmlTextWriter.TagRightChar);
output.WriteEndTag("iframe");
output.WriteLine();
}
else
{
// Server-side generation
try
{
// Render the report via SOAP
byte[] result = RenderReport();
// render the report as HTMLFragment for html reports
if (this.IsHtmlReport)
{
// this an html report
// so just stream it back as fragment
string res = ASCIIEncoding.ASCII.GetString(result);
output.Write(res);
}
else
{
// some other export format was requested
// stream the payload in binary format
HttpResponse response = System.Web.HttpContext.Current.Response;
// response.ClearContent();
// response.ClearHeaders();
string fileName = GetFileName(this.ReportPath, this.Format);
response.ContentType = GetContentType(Path.GetExtension(fileName));
response.AddHeader ("content-disposition", "attachment; filename=\"" + fileName + "\"");
// response.BinaryWrite(result);
// response.Flush();
// response.Close();
response.BinaryWrite(result);
}
}
catch (SoapException ex)
{
output.Write(MessageToHTML(ex.Detail.OuterXml));
}
catch (System.Exception ex)
{
output.Write(MessageToHTML(ex.ToString()));
}
}
}
#endregion
#region My methods
/// <summary>
/// Renders the report via SOAP
/// </summary>
/// <returns></returns>
private byte[] RenderReport()
{
ReportingService rs = new ReportingService();
rs.Url = this._serverUrl;
rs.Credentials = System.Net.CredentialCache.DefaultCredentials;
// Render arguments
byte[] result = null;
string reportPath = this.ReportPath;
string historyID = null;
string format = this.Format;
string devInfo = null; // not supported for now
DataSourceCredentials[] credentials = null;
string showHideToggle = null;
string encoding;
string mimeType;
Warning[] warnings = null;
ParameterValue[] reportHistoryParameters = null;
string[] streamIDs = null;
ParameterValue[] reportParameters = null;
string optionalString = null;
string imageDownloadPath = null;
SessionHeader sh = new SessionHeader();
// set the report parameters
if (_parameterValues.Count>0)
{
reportParameters = new ParameterValue[_parameterValues.Count];
int i = 0;
foreach (DictionaryEntry parameter in _parameterValues )
{
reportParameters[i] = new ParameterValue();
reportParameters[i].Name = parameter.Key.ToString();
reportParameters[i].Value = parameter.Value.ToString();
i++;
}
}
try
{
// set the streamrootpath so the Report Server can save the images to this folder
if (System.Web.HttpContext.Current != null)
{
imageDownloadPath = System.Web.HttpContext.Current.Request.PhysicalApplicationPath +
this.ImageDownloadPath + Path.DirectorySeparatorChar;
}
// if ImageDownloadPath is not specified, default to the StreamRoot path
// NOTE: Defaulting to StreamRoot will not work for Internet reports
// Please refer to Chapter 11 for a detailed discussion.
devInfo = AddDeviceSetting("StreamRoot", (this.StreamRoot==String.Empty) ? imageDownloadPath:this.StreamRoot, devInfo);
// render the report as HTMLFragment for html reports
if (this.IsHtmlReport)
{
devInfo = AddDeviceSetting("HTMLFragment", "true", devInfo);
}
result = rs.Render(reportPath, format, historyID, devInfo, reportParameters, credentials,
showHideToggle, out encoding, out mimeType, out reportHistoryParameters, out warnings,
out streamIDs);
// for multi-stream renders, such as HTML, need to render the report images as streams
// so they show in the report
// alternatively, if HTTP GET is allowed on the Report Server,
// the HTMLFragment device info setting could be used
if (this.IsHtmlReport)
{
foreach (string streamID in streamIDs)
{
byte [] image = rs.RenderStream(reportPath, format, streamID, null, null, reportParameters, out optionalString, out optionalString);
FileStream stream = File.OpenWrite(imageDownloadPath + streamID);
stream.Write(image, 0, image.Length);
stream.Close();
}
}
}
catch (SoapException ex)
{
Trace.WriteLine(ex.Detail.OuterXml);
throw ex;
}
return result;
}
/// <summary>
/// A simple helper function to add a setting to device info
/// </summary>
/// <param name="settingName"></param>
/// <param name="settingValue"></param>
/// <param name="devInfo"></param>
/// <returns></returns>
private static string AddDeviceSetting (string settingName, string settingValue, string devInfo)
{
if (devInfo == null) devInfo = "<DeviceInfo/>";
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml (devInfo);
XmlNode streamRoot = xmlDoc.CreateNode(XmlNodeType.Element, settingName, String.Empty );
streamRoot.InnerText = settingValue;
xmlDoc.ImportNode(streamRoot, false);
xmlDoc.DocumentElement.AppendChild(streamRoot);
return xmlDoc.InnerXml;
}
private string MessageToHTML(string message)
{
return "<P style=\"font-family: Verdana; font-size: 11px\">" + message + "</P>" ;
}
private static string GetFileName (string reportPath, string format)
{
string fileName = Path.GetFileName(reportPath);
string fileExt = null;
switch (format.ToLower())
{
case "html4.0" : fileExt = "html"; break;
case "html3.2" : fileExt = "html"; break;
case "mhtml" : fileExt = "html"; break;
case "htmlowc" : fileExt = "html"; break;
case "excel" : fileExt = "xls"; break;
case "jpeg" : fileExt = "jpg"; break;
case "tiff" : fileExt = "tif"; break;
case "image" : fileExt = "tif"; break;
default : fileExt = format; break;
}
fileName = fileName + "." + fileExt;
return fileName;
}
/// <summary>
/// Return the content type of the response when the report is streamed back to the browser
/// </summary>
/// <param name="fileExtension"></param>
/// <returns></returns>
private string GetContentType(string fileExtension)
{
string contentType = null;
switch (fileExtension.ToLower())
{
case ".pdf" : contentType = "application/pdf"; break;
case ".tif" : contentType = "image/tiff"; break;
case ".xls" : contentType = "x-msexcel"; break;
case ".xml" : contentType = "text/xml"; break;
default : contentType = "text/plain"; break;
}
return contentType;
}
#endregion
#region ICustomTypeDesciptor implementation
// in all cases except GetProperties pass-through
public System.ComponentModel.AttributeCollection GetAttributes()
{
// TODO: Add AwReportViewer.GetAttributes implementation
return TypeDescriptor.GetAttributes(this.GetType());
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this.GetType());
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this.GetType());
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this.GetType());
}
public Object GetEditor(System.Type editorBaseType)
{
return TypeDescriptor.GetEditor(this.GetType(), Type.GetType("System.Drawing.Design.UITypeEditor"));
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(this.GetType());
}
public EventDescriptorCollection GetEvents(System.Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this.GetType(), attributes);
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public PropertyDescriptorCollection GetProperties(System.Attribute[] attributes)
{
PropertyDescriptorCollection filteredProperties = new PropertyDescriptorCollection(null);
PropertyDescriptorCollection existingProperties;
PropertyDescriptor tempProperty = null;
existingProperties = TypeDescriptor.GetProperties(this.GetType(), attributes);
foreach (PropertyDescriptor pd in existingProperties) filteredProperties.Add(pd);
// enable/disable the custom properties based on the report generation option
tempProperty = filteredProperties["ToolbarVisible"];
if (tempProperty !=null)
{
filteredProperties.Remove(tempProperty);
tempProperty = TypeDescriptor.CreateProperty(tempProperty.ComponentType, tempProperty, new System.Attribute[] {this.ServerSide == multiState.True?ReadOnlyAttribute.Yes:ReadOnlyAttribute.No});
filteredProperties.Add(tempProperty);
}
tempProperty = filteredProperties["ParametersVisible"];
if (tempProperty !=null)
{
filteredProperties.Remove(tempProperty);
tempProperty = TypeDescriptor.CreateProperty(tempProperty.ComponentType, tempProperty, new System.Attribute[] {this.ServerSide == multiState.True?ReadOnlyAttribute.Yes:ReadOnlyAttribute.No});
filteredProperties.Add(tempProperty);
}
tempProperty = filteredProperties["Zoom"];
if (tempProperty !=null)
{
filteredProperties.Remove(tempProperty);
tempProperty = TypeDescriptor.CreateProperty(tempProperty.ComponentType, tempProperty, new System.Attribute[] {this.ServerSide == multiState.True?ReadOnlyAttribute.Yes:ReadOnlyAttribute.No});
filteredProperties.Add(tempProperty);
}
tempProperty = filteredProperties["StreamRoot"];
if (tempProperty !=null)
{
filteredProperties.Remove(tempProperty);
tempProperty = TypeDescriptor.CreateProperty(tempProperty.ComponentType, tempProperty, new System.Attribute[] {this.ServerSide == multiState.True?ReadOnlyAttribute.No:ReadOnlyAttribute.Yes});
filteredProperties.Add(tempProperty);
}
tempProperty = filteredProperties["ImageDownloadPath"];
if (tempProperty !=null)
{
filteredProperties.Remove(tempProperty);
tempProperty = TypeDescriptor.CreateProperty(tempProperty.ComponentType, tempProperty, new System.Attribute[] {this.ServerSide == multiState.True?ReadOnlyAttribute.No:ReadOnlyAttribute.Yes});
filteredProperties.Add(tempProperty);
}
tempProperty = filteredProperties["DataSource"];
if (tempProperty !=null && this.ServerSide!=multiState.True)
{
filteredProperties.Remove(tempProperty);
}
return filteredProperties;
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
// TODO: Add AwReportViewer.GetPropertyOwner implementation
return this;
}
#endregion
}
}