Reporting in Silverlight

Reporting in Silverlight environment has one major problem.  Silverlight does not have a report viewer control for either SQL Server reporting services or Crystal reports.  As a result, one has to come up with a workaround that uses existing report viewer controls.  Luckily both Crystal and SSRS have report viewer controls designed for the web.  So, all we need to do is cleanly integrate these controls into a Silverlight application, using browser integration capabilities of Silverlight.  By cleanly, I mean as cleanly as possible :-).

We have two options to do this.  We can launch another browser window from Silverlight and open the report in new ASPX page or we can embed a new web page that hosts report viewer control inside the web page that hosts Silverlight application.  I will demonstrate these options in the following manner.  Launch a new web page that hosts SSRS report viewer and embed a web page that hosts Crystal reports viewer.

This post builds on top of previous post I had about SSRS implementation.

SSRS implementation

Create ASPX page for report viewer:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ReportViewerForm.aspx.cs"
    Inherits="MyWeb.ReportViewerForm" %>
<!–
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">–>
<%@ Register Assembly="Microsoft.ReportViewer.WebForms, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
    Namespace="Microsoft.Reporting.WebForms" TagPrefix="rsweb" %>
<html xmlns="http://www.w3.org/1999/xhtml" style="height:100%;">
<head runat="server">
    <title>Report</title>
</head>
<body>
    <form id="form1" runat="server" style="height:100%" >
    <div style="height:100%" >
        <rsweb:ReportViewer ID="MainReportViewer" runat="server" 
            AsyncRendering="false"
            ProcessingMode="Remote"
            Width="100%"
            ShowExportControls="True"
            ShowFindControls="True"
            ShowParameterPrompts="False"
            ShowPromptAreaButton="False"
            ShowRefreshButton="False"
            BackColor="White">
            <ServerReport DisplayName="MainReport" ReportServerUrl="" />
        </rsweb:ReportViewer>
    </div>
    </form>
</body>
</html>

Create a class to launch a report with the following routine:

public static void LaunchReport(string reportName)
       {
           Uri sourceUri = new Uri(HtmlPage.Document.DocumentUri, Application.Current.Host.Source.ToString().Substring(0, Application.Current.Host.Source.ToString().IndexOf("ClientBin") – 1) + "/ReportViewerForm.aspx?ReportName=" + reportName);

           if (ArePopupsAllowed())
           {
               System.Text.StringBuilder codeToRun = new System.Text.StringBuilder();
               codeToRun.Append("window.open(");
               codeToRun.Append(""");
               codeToRun.Append(sourceUri.ToString());
               codeToRun.Append("",");
               codeToRun.Append(""");
               codeToRun.Append("",");
               codeToRun.Append(""");
               codeToRun.Append("width=1100,height=900,scrollbars=yes,menubar=no,toolbar=no,resizable=yes");
               codeToRun.Append("");");
               try
               {
                   HtmlPage.Window.Eval(codeToRun.ToString());
               }
               catch (Exception ex)
               {
                   // do something               }
           }

           else
              //inform the user that popups need to be enabled
       }

ArePopupsAllowed function tests if popups are allowed in the browser.  Contact me if you need this code.

If you need to pass in parameters, you can easily pass them in as query parameters and parse them inside  ReportViewerForm.aspx just like the report name itself.  Here is code in Load event for this web page:

protected void Page_Load(object sender, EventArgs e)
        {
            MainReportViewer.ProcessingMode = ProcessingMode.Remote;
            MainReportViewer.ServerReport.ReportPath = "/MySSRSReports/" + this.Request.QueryString["ReportName"];
            MainReportViewer.ServerReport.ReportServerUrl = new Uri("http://localhost:8080/ReportServer_SQL2008");
            List<ReportParameter> parameters = new List<ReportParameter>();
            List<string> values = new List<string>();
            values.Add("1");
            values.Add("2");
            values.Add("3");
            ReportParameter oneParamter = new ReportParameter("ParmeterName", values.ToArray());
            parameters.Add(oneParamter);
            MainReportViewer.ServerReport.SetParameters(parameters.ToArray());
            MainReportViewer.ShowParameterPrompts = false;
            MainReportViewer.ServerReport.Refresh();
        }

In the example above I do hard code parameter values, but you get the idea.  Just add your parameters to the URL for ReprotViewerForm and have the form add them to each report.

Of course you also need to create SSRS report as well, but this is a separate subject.

Crystal Reports implementation

First thing is to create a crystal report.  You do have the usual options here to have the report get data directly from database (which is what I am using) or you can write more code to get data into a DataSet for example, using additional .NET classes and pass DataSet to the report.  We have to create a web page here as well to host report viewer.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="ReportView.aspx.cs" Inherits="MyApp.Web.ReportView" %>

<%@ Register assembly="CrystalDecisions.Web, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304" namespace="CrystalDecisions.Web" tagprefix="CR" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <CR:CrystalReportViewer ID="CrystalReportViewer1" runat="server"
            AutoDataBind="true" />
    </div>
    </form>
</body>
</html>

Again, you have to put some code to Load event to interpret report name and parameters as well as setup connection information

protected void Page_Load(object sender, EventArgs e)
        {
            string filter = Request.Params["param"];
            CrystalReportViewer1.Attributes.Add("Width", "100%");
            CrystalReportViewer1.Attributes.Add("Width", "100%");

            CrystalDecisions.CrystalReports.Engine.ReportDocument doc = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
            doc.Load(Server.MapPath("MyReport.rpt"));
            foreach (CrystalDecisions.CrystalReports.Engine.Table item in doc.Database.Tables)
            {
                CrystalDecisions.Shared.ConnectionInfo connection = new CrystalDecisions.Shared.ConnectionInfo();
                connection.ServerName = "(local)";
                connection.UserID = "myUser";
                connection.Password = "myPassword";
                connection.DatabaseName = "MyDatabase";
                CrystalDecisions.Shared.TableLogOnInfo logInfo = new CrystalDecisions.Shared.TableLogOnInfo();
                logInfo.ConnectionInfo = connection;
                item.ApplyLogOnInfo(logInfo);
            }

            CrystalDecisions.Shared.ParameterField param = new CrystalDecisions.Shared.ParameterField();

            CrystalDecisions.Shared.ParameterDiscreteValue discreteVal = new CrystalDecisions.Shared.ParameterDiscreteValue();

            discreteVal.Value = filter;
            param.ParameterFieldName = "@Test";
            param.CurrentValues.Add(discreteVal);
            doc.SetParameterValue(param.ParameterFieldName, param.CurrentValues);

            this.CrystalReportViewer1.ReportSource = doc;
        }

So far pretty easy.  How the Silverlight part.  In this case we do not want to have a popup, but instead have our page appear to be a part of Silverlight application.  Let’s see what web page looks like that hosts Silverlight application.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="MyApp.Web._Default" %>

<%@ Register Assembly="System.Web.Silverlight" Namespace="System.Web.UI.SilverlightControls"
    TagPrefix="asp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%;">
<head id="Head1" runat="server">
    <title>Report</title>
</head>
<body style="height: 100%; margin: 0;">
    <form id="form2" runat="server" style="height: 100%;">
    <asp:ScriptManager ID="ScriptManager2" runat="server">
    </asp:ScriptManager>
    <div style="height: 100%;">
        <asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/MySilverlightApp.xap" MinimumVersion="2.0.30523"
            Width="100%" Height="80%" Windowless="true"/>
    </div>
    <iframe id="reportFrame" style="position:absolute; width:0px; height:0px; visibility:hidden"></iframe>
    </form>
</body>

</html>

As you can see, we are using frames in this case to show the report.  Now, the Silverlight code to launch a report:

private void ShowReportButton()        {
            HtmlElement m = HtmlPage.Document.GetElementById("reportFrame");
            if (m != null)
            {
                int top, left, width, height;
                top = (int)this.outerGrid.RowDefinitions[0].ActualHeight;
                left = (int)(this.outerGrid.ColumnDefinitions[0].ActualWidth + this.outerGrid.ColumnDefinitions[1].ActualWidth);
                width = (int)this.outerGrid.ActualWidth – left;
                height = (int)this.outerGrid.ActualHeight – top;
                m.SetStyleAttribute("left", left.ToString());
                m.SetStyleAttribute("top", top.ToString());
                m.SetStyleAttribute("width", width.ToString());
                m.SetStyleAttribute("height", height.ToString());
                m.SetAttribute("src", "ReportView.aspx?param=P");
                m.SetStyleAttribute("visibility", "visible");
            }
        }

In the code about outerGrid is the control that hosts main Silverlight application.  So, since our Silverlight application is located at position (0,0) in the web page (top left corner), we can compute the coordinates and size of report frame by using actual height and width of our Silverlight controls.  Once that is done, we can control the location of the frame and its size to make it appear as part of Silverlight application.  You would also need to listen to resize events of the browser in order to control the size of the report window.

This is my attempt to write a comprehensive guide to Silverlight reporting.

One Comment

Leave a Reply to Ernesto Herrera Cancel reply

Your email address will not be published. Required fields are marked *