Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight Report Viewer using View Model (MVVM)

0.00/5 (No votes)
13 Dec 2011 1  
Viewing .rdlc reports from Silverlight using View Model (MVVM)

Viewing And Printing Reports in a Silverlight Application

Reports are an important part of a Silverlight Line of Business (LOB) application. While Silverlight 4 does provide printing capabilities, multi-page printing is still challenging. In addition, a lot of businesses have already invested considerable resources into learning to produce reports using the report designer in Visual Studio.

While there are some 3rd party controls that will display a report in Silverlight, they cost money. Also, some require you to use the full Microsoft SQL Server Reporting Services (SSRS), and that may not be an option for a lot of people. An .rdlc report created with Visual Studio does not require SSRS.

The method we will demonstrate in this article was developed by Sushant Pandey. He describes it here. The difference in this implementation is that we will use Behaviors to make it fully MVVM compliant (he uses code behind in his implementation).

Printing A Report

We will start with the Simple Silverlight Configurator/Pivot (View Model/MVVM) I published a few months ago.

The application allows the user to change the Gender, Weight, and Age selectors, and it dynamically filters the list of people.

The application has been enhanced to allow the user to click the Show Report Viewer button and the report will show in a popup window.

If they click Print PDF Report, they will see a PDF report in a popup window.

I actually prefer the Report Viewer because the user sees an animation while waiting for the report to render.

Creating the Report

The first step is to add a Report to the project.

The project already contains the following web service:

[WebMethod]
public List<Person> SearchPeople(string Gender, int AgeHigh, int AgeLow,
    int WeightHigh, int WeightLow)
{
    List<Person> colPeople = new List<Person>();
    
    var result = from objPeople in People.GetPeople().AsQueryable()
                 where objPeople.Age <= AgeHigh
                 where objPeople.Age >= AgeLow
                 where objPeople.Weight <= WeightHigh
                 where objPeople.Weight >= WeightLow
                 select objPeople;
                 
    if (Gender != "All")
    {
        colPeople = (from finalresult in result
                     where finalresult.Gender == Gender
                     select finalresult).ToList();
    }
    else
    {
        colPeople = result.ToList();
    }
    
    return colPeople;
}

We are able to select that web service as a dataset for the report.

After designing the report, we place a report control on a .aspx page.

The following code, in the code behind for the page, is used to accept report parameters, and to display the Report Viewer Control (ShowReportViewer()), or to render a .pdf version (RenderReport()):

public partial class Report : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        // If parameters are passed just render report
        if
            (
            Request.QueryString["Gender"] != null &&
            Request.QueryString["AgeHigh"] != null &&
            Request.QueryString["AgeLow"] != null &&
            Request.QueryString["WeightHigh"] != null &&
            Request.QueryString["WeightLow"] != null
            )
        {
            if (Request.QueryString["ShowReportViewer"] == "False")
            {
                RenderReport();
            }
            else
            {
                if (!Page.IsPostBack)
                {
                    ShowReportViewer(); 
                }
            }
        }
    }
    
    #region RenderReport
    private void RenderReport()
    {
        // CheckBox to see if there is any data
        if (LoadData().Rows.Count > 0)
        {
            LocalReport localReport = new LocalReport();
            localReport.ReportPath = Server.MapPath("~/Report1.rdlc");
            
            ReportDataSource reportDataSource = 
		new ReportDataSource("DataSet1", LoadData());
            localReport.DataSources.Add(reportDataSource);
            
            string reportType = "pdf";
            string mimeType = "application/pdf";
            string encoding;
            string fileNameExtension;
            
            //The DeviceInfo settings should be changed based on the reportType
            //http://msdn2.microsoft.com/en-us/library/ms155397.aspx
            string deviceInfo =
            "<DeviceInfo>" +
            " <OutputFormat>PDF</OutputFormat>" +
            " <PageWidth>8.5in</PageWidth>" +
            " <PageHeight>11in</PageHeight>" +
            " <MarginTop>0.5in</MarginTop>" +
            " <MarginLeft>1in</MarginLeft>" +
            " <MarginRight>1in</MarginRight>" +
            " <MarginBottom>0.5in</MarginBottom>" +
            "</DeviceInfo>";
            
            Warning[] warnings;
            string[] streams;
            byte[] renderedBytes;
            
            //Render the report
            renderedBytes = localReport.Render(
            reportType,
           deviceInfo,
           out mimeType,
           out encoding,
           out fileNameExtension,
           out streams,
           out warnings);
           
            //Clear the response stream and write the bytes to the outputstream
            //Set content-disposition to "attachment" 
            //so that user is prompted to take an action
            //on the file (open or save)
            Response.Clear();
            Response.ContentType = mimeType;
            //Response.AddHeader("content-disposition", 
		"attachment; filename=foo." + fileNameExtension);
            Response.BinaryWrite(renderedBytes);
            Response.End();
        }
        else
        {
            // There were no records returned
            Response.Clear();
            //Response.AddHeader("content-disposition", 
		"attachment; filename=foo." + fileNameExtension);
            Response.Write("No Data");
            Response.End();
        }
    }
    #endregion
    
    #region ShowReportViewer
    private void ShowReportViewer()
    {
        this.ReportViewer1.ProcessingMode = ProcessingMode.Local;
        this.ReportViewer1.LocalReport.ReportPath = Server.MapPath("~/Report1.rdlc");
        ReportViewer1.LocalReport.DataSources.Add(
           new ReportDataSource("DataSet1", LoadData()));
    }
    #endregion

The following method provides data for the report at run-time:

#region LoadData
private DataTable LoadData()
{
    string Gender = Convert.ToString(Request.QueryString["Gender"]);
    int AgeHigh = Convert.ToInt32(Request.QueryString["AgeHigh"]);
    int AgeLow = Convert.ToInt32(Request.QueryString["AgeLow"]);
    int WeightHigh = Convert.ToInt32(Request.QueryString["WeightHigh"]);
    int WeightLow = Convert.ToInt32(Request.QueryString["WeightLow"]);
    
    var result = from objPeople in People.GetPeople().AsQueryable()
                 where objPeople.Age <= AgeHigh
                 where objPeople.Age >= AgeLow
                 where objPeople.Weight <= WeightHigh
                 where objPeople.Weight >= WeightLow
                 select objPeople;
                 
    if (Gender != "All")
    {
        result = from finalresult in result
                 where finalresult.Gender == Gender
                 select finalresult;
    }
    
    Utility objUtility = new Utility();
    DataTable dt = objUtility.LINQToDataTable(result);
    
    return dt.DefaultView.Table;
}
#endregion

Note, that it uses a class LINQToDataTable, that converts the Linq to SQL query into a DataTable that the report needs.

Also note that we don't actually pass parameters to the Report Viewer control. With a .rdlc report, we simply provide the data for the report. We must filter the Linq query to filter the report.

It may also seem odd that we designed the report directly against the web service method, but we have to supply the data in the code behind. You can see this site for answers as to why the Report Viewer control has been designed this way: http://www.gotreportviewer.com.

The Silverlight Project

In the Silverlight project, we add ReportPDFURL and ReportViewerURL properties to the View Model. We also add a SetReportUrls method that is triggered whenever a supporting property is changed:

private void SetReportUrls()
{
    // Set the Report URLs
    ReportViewerURL = String.Format("{0}Gender={1}&AgeHigh={2}&AgeLow={3}&
			WeightHigh={4}&WeightLow={5}&ShowReportViewer=True",
            GetBaseAddress(), Gender, AgeHigh.ToString(), AgeLow.ToString(), 
			WeightHigh.ToString(), WeightLow.ToString());

    ReportPDFURL = String.Format("{0}Gender={1}&AgeHigh={2}&AgeLow={3}&
			WeightHigh={4}&WeightLow={5}&ShowReportViewer=False",
        GetBaseAddress(), Gender, AgeHigh.ToString(), AgeLow.ToString(), 
			WeightHigh.ToString(), WeightLow.ToString());
} 

We create the following Behavior that will open up the HTML Popup window:

namespace Behaviors
{
    [System.ComponentModel.Description("Opens an HTML Window")]
    public class HTMLPopupWindow : TargetedTriggerAction<Button>, INotifyPropertyChanged
    {
        #region PopupURL
        public static readonly DependencyProperty PopupURLProperty = 
				DependencyProperty.Register("PopupURL",
            typeof(string), typeof(HTMLPopupWindow), null);
        public string PopupURL
        {
            get
            {
                return (string)base.GetValue(PopupURLProperty);
            }
            set
            {
                base.SetValue(PopupURLProperty, value);
                this.NotifyPropertyChanged("PopupURL");
            }
        }
        #endregion

        #region PopupWidth
        public static readonly DependencyProperty PopupWidthProperty = 
				DependencyProperty.Register("PopupWidth",
            typeof(int), typeof(HTMLPopupWindow), null);
        public int PopupWidth
        {
            get
            {
                return (int)base.GetValue(PopupWidthProperty);
            }
            set
            {
                base.SetValue(PopupWidthProperty, value);
                this.NotifyPropertyChanged("PopupWidth");
            }
        }
        #endregion

        #region PopupHeight
        public static readonly DependencyProperty PopupHeightProperty = 
				DependencyProperty.Register("PopupHeight",
            typeof(int), typeof(HTMLPopupWindow), null);
        public int PopupHeight
        {
            get
            {
                return (int)base.GetValue(PopupHeightProperty);
            }
            set
            {
                base.SetValue(PopupHeightProperty, value);
                this.NotifyPropertyChanged("PopupHeight");
            }
        }
        #endregion

        protected override void OnAttached()
        {
            base.OnAttached();
        }

        protected override void Invoke(object parameter)
        {
            if (true == HtmlPage.IsPopupWindowAllowed)
            {
                System.Text.StringBuilder codeToRun = new System.Text.StringBuilder();
                codeToRun.Append("window.open(");
                codeToRun.Append("\"");
                codeToRun.Append(string.Format("{0}", PopupURL));
                codeToRun.Append("\",");
                codeToRun.Append("\"");
                codeToRun.Append("\",");
                codeToRun.Append("\"");
                codeToRun.Append("width=" + PopupWidth.ToString() + 
				",height=" + PopupHeight.ToString());
                codeToRun.Append(",scrollbars=yes,menubar=no,toolbar=no,resizable=yes");
                codeToRun.Append("\");");
                try
                {
                    HtmlPage.Window.Eval(codeToRun.ToString());
                }
                catch
                {
                    MessageBox.Show("You must enable popups to view reports. 
			Safari browser is not supported.",
                        	"Error", MessageBoxButton.OK);
                }
            }
            else
            {
                MessageBox.Show("You must enable popups to view reports. 
			Safari browser is not supported.",
                    	"Error", MessageBoxButton.OK);
            }
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
        }

        // Utility

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

Next, we add buttons to the View.

We add the HTMLPopupWindow Behavior to the Buttons.

We then bind the PopupURL to the appropriate property in the View Model, and set the other parameters.

Not Really Printing In Silverlight

The key thing with this implementation is that we are not actually printing "inside" Silverlight. However, the end user will most likely not care. They will have the functionality they need. As a developer, this method should allow you to deliver a professional solution at a reasonable cost.

Deployment

You will need to use ASP.NET 4.0, and install the Microsoft Report Viewer 2010 Redistributable Package on the web server that displays the report.

History

  • 24th October, 2010: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here