This article is compatible with the latest version of Silverlight.
Introduction
In Part 5 of this series I looked into creating a form to maintain the details of products in the inventory. In this article I will look at implementing reporting functionality within the Silverlight application.
Source Code and Live Demo*
Instructions on setting up the sample project can be found in Part 1.
*To log into the sample application use the following Username: demo and Password: demo
Be sure to check all articles of the series: Part 1, Part 2, Part 3, Part 4, Part 5 and Part 6
Reporting Requirements
Businesses need to be able to store their data, but they also want to get information out of the system to track and monitor various aspects of the business such as performance, cash flow, forecasts, sales vs targets, etc. An ideal means of achieving this is through implementing reporting functionality in the application. A report generally consists of data retrieved from one or more databases inserted into a predefined template – often with some sort of processing in between to turn the data into useful information. Reports can range from the extremely simple to the extremely complex, but both follow the same workflow from an application perspective. At runtime, data is extracted from the database and is passed through to the report generator/writer which processes the data and formats the results suitable for printing in a human readable form. The complexity of processing the data and formatting the results is handled by the report generator based upon the rules defined in the template. Users won’t always want to have to produce reports for information that they regularly monitor but don’t print – this functionality is better implemented within the dashboard. Therefore reporting is most appropriately used when printing is required (which it is in most line of business applications).
Examples of simple reports include invoices and data printouts, which require minimal processing and generally just insert data from a database directly into the report. More complex reporting may involve mining the data in the database to determine patterns or relationships (correlations) between various data points, or create predictive models upon which management can base their ongoing tactics and to help determine their future strategies. Some reports may take some time to produce when a lot of processing is required, and if generating reports places a high load on the web server then this process may be offloaded to a separate dedicated server (such as SQL Server Reporting Services). So to throw in a few buzz words, reporting helps enable business intelligence and knowledge management from within your line of business application.
There are reporting solutions for Windows Forms and ASP.NET provided with each copy of Visual Studio (Crystal Reports for Visual Studio in the Professional or higher editions, and Visual Studio Report Designer in the Standard or higher editions and in the Visual Web Developer 2008 Express Edition after installing an add-in). However, neither of these generates reports that can be viewed or printed within a Silverlight application. The only existing solution I have found is a third party solution called Report Sharp-Shooter for Silverlight by Perpetuum Software. I haven’t tried it personally, though based upon the online demo and the feature list you can’t print the report directly from the Silverlight application – the user still needs to export it to another format (such as a PDF file) before they can print the report, which is not the streamlined solution I’m trying to achieve. The reason why printing wouldn’t be implemented is because in actual fact Silverlight currently has no ability to send data to a printer. Therefore we need to look outside Silverlight for a solution.
The strategy I have decided to use therefore is to use existing reporting tools to generate the reports and display these somehow within the application. I chose to focus on generating the reports using the Visual Studio Report Designer as it’s a reasonably capable reporting tool (it uses the same template format as used by SQL Server Reporting Services), and it’s the reporting tool that all Silverlight developers can use, being provided with Standard and higher editions of Visual Studio. Note that Microsoft has just released an add-in to provide the Visual Web Developer 2008 Express Edition with a report builder. They previously had one for the 2005 edition but had not up until now released a version for the 2008 edition. If you are using the Express Edition, you can download the add-in here.
The output of the reporting engine can be either HTML (when hosted within the ASP.NET Report Viewer Control), an Excel document, an image, or a PDF document, each of which can be displayed within a browser (HTML and images natively, Excel and PDF documents by browser plug-ins). Only the image rendering however can be displayed directly within a Silverlight application, but is not a particularly suitable option due to the large file size of the images and that the quality of the printouts would be low resolution when attempting to print them. Therefore we need to look at means of rendering the report outside of the Silverlight application, using the capabilities of the browser instead.
Of the three remaining options we can exclude rendering to Excel as it would only generally be appropriate to do so when generating matrix or table based reports. We can now (using the 2008 version) print directly from ASP.NET’s Report Viewer control, but it requires the installation of an ActiveX control which will only work in Internet Explorer running on Windows (not Macs) and not within Firefox. You want your Silverlight application to run cross browser and cross platform so I consider this to be an unnecessary restriction to put on your application in most scenarios, and I believe printing is a vital feature for implementing reporting. This leaves PDF as our only viable option, though it does provide the functionality we require. PDF documents display on the screen exactly as they appear on the printed page, they can be printed directly from within the browser where they are being viewed, they can be saved to the user’s hard disk (or a network drive) for archiving purposes, they can be easily emailed to colleagues, and they can be published to content management / collaboration / document management systems such as Microsoft SharePoint. In other words it provides the best solution to fit the majority of report display requirements.
As noted however, you cannot view a PDF document from within a Silverlight application (without writing an entire PDF renderer for Silverlight). Opening the report up in another browser window would be a possible solution but is not particularly suitable in all cases as it separates the display of the report from the application. Therefore we need to look to other options to achieve this. Our application was designed to fill all the available area in the browser window, so having the application and the report within the same browser frame is not really a viable solution. There is a technique however to overlay a part of the application with an IFrame (contained within the same HTML file that hosts the Silverlight application) to achieve the required effect.
HTML Viewer Control
To support displaying an IFrame within the application I decided to wrap the functionality into a reusable user control. This way wherever support is required in this or other applications for displaying HTML content within the application itself we can easily drop it in. This control creates an IFrame in the underlying html page using the HTML Bridge, and is overlaid on top of the Silverlight plug-in, with the HTML content to display loaded within it.
Before we describe how this control works, let’s take a quick detour to discuss the HTML Bridge. Essentially the HTML Bridge is the means to interact with the underlying browser and document object model (DOM) from a Silverlight application. You may also see this referenced as HTML DOM Interoperability. Throughout this article I use the HTML Bridge to provide various features such as opening a popup window, creating elements in the DOM (such as an IFrame), inspecting the DOM (checking properties of the Silverlight plug-in), creating cookies, and handling JavaScript events. To access the HTML Bridge, add a reference to the System.Windows.Browser namespace, and then you can access the DOM of the underlying HTML document that is hosting the plug-in via the static HTMLPage class.
Now that we know a bit about the HTML Bridge let’s look at some of the issues that we will face in displaying an IFrame over the top of our Silverlight plug-in. The primary issue that we face is that by default the Silverlight plug-in is in “windowed” mode, where the plug-in handles rendering the Silverlight content in its own window, which is on top of any HTML content in the underlying HTML document. Therefore even though we are able to create the IFrame in the underlying HTML document it won’t be visible as our Silverlight plug-in occupies the entire browser area, rendering on top of any HTML content in the DOM of the page (regardless of the z-index of the plug-in or the IFrame). Therefore we need to turn this windowed mode off so that the browser is in charge of the rendering instead of the Silverlight plug-in, and then our Silverlight content will be rendered alongside the underlying HTML content. There are performance issues however in placing the browser in charge of the rendering, so the decision to use windowless mode needs to be carefully considered. Silverlight is optimised for rendering complex animations and video and there will be a significant performance loss in these two areas when using windowless mode. Considering the fact that we are building a line of business application here the loss in rendering performance is probably not going to affect us in most cases, so the benefits in this case should outweigh the costs. Karl Erickson has an excellent blog entry containing other limitations of windowless mode that is worth reading – the link can be found in the Resources at the end of this article.
To enable windowless mode we need to open the ASPX page hosting the Silverlight plug-in, find the asp:Silverlight tag, and add a Windowless attribute – setting its value to “true”. When this renders to the output page some param tags are added as children of the plug-in and this property will be one of them. Since we are creating a reusable control it’s worth checking to make sure the developer has turned on this windowless property and raise an exception if not, in order to save confusion as to why the IFrame isn’t being displayed. With the HTML Bridge we can navigate through the DOM of the page and ensure this property is turned on. First we need to get a reference to the plug-in element (which is rendered as an object tag). Rather than searching for the object tag in the page, we can simply get a reference to it using HTMLPage.Plugin. Then we can enumerate through its children looking for param tags that have a name attribute of “Windowless”. If the tag isn’t found or its value is set to False then we can throw an exception.
The next step is to create the IFrame in the underlying HTML page. The IFrame is contained within a DIV element so that we can set its position properly. For this DIV we’ll set its z-index to 99 (so it displays on top of other controls, assuming their z-index is less than 99), and set its positioning mode to “absolute”. This means we can set its position by its coordinates in the browser window. We can then append this DIV as a child of the FORM tag in the DOM, and append the IFrame element as a child of the DIV tag.
Now that we have created the required elements in the DOM we need to position and size them. As previously mentioned this involves setting the coordinates and size of the DIV, and we will also set the height and width of the IFrame to support multiple browsers (in some browsers the IFrame fills the area of the DIV, and in others it doesn’t). This involves working out the location of the control with reference to the application root visual element (ie. the whole area occupied by the Silverlight plug-in) by creating a transform object to transform the coordinates of the two and relating (0, 0) on the control to (0, 0) on the application root visual element. This gives us the coordinates of the control with reference to the application root visual element, but not necessarily with reference to the browser window itself (if the Silverlight control is not located at (0, 0) to the browser window). Therefore for completeness we should calculate the offset of the Silverlight plug-in in the underlying page (moving up the DOM and adding offsets of each parent element) and add that to our calculations. As our Silverlight application is filling the entire browser window it will be located at (0, 0) making this calculation unnecessary, so I have chosen to make this an assumption and forgo these additional calculations. Therefore we now can use these coordinates to set the “left” and “top” properties of the DIV, and the “width” and “height” properties are simply the width and height of the control.
With writing a control such as this where the content being displayed is not being rendered by Silverlight we need to take resizing of the control into account. If the control is resized (such as if the browser window was resized or a grid splitter was implemented that enabled the user to resize or move the location of the control) we need to handle the LayoutChanged event of the control and adjust the size and location of the IFrame accordingly. Note that I don’t handle the SizeChanged event for this purpose – the reason being that if the control location was simply moved (rather than resized) the SizeChanged event is not raised. Therefore although the LayoutChanged event is raised even when the control hasn’t been moved or resized (and can be raised many times for many reasons as it is raised when anything occurs in the visual tree), it’s the only event that will handle all situations – even if it isn’t ideal (I try to never handle this event if an alternative is available due to the numerous times and purposes that it can be raised).
I have included support for monitoring when the page has loaded (handling the JavaScript onLoad event raised by the IFrame via the HTML Bridge), in turn raising an event on this control to notify the application as such. This could potentially be useful in showing an animation and/or message in the footer of the application (as server data requests do) indicating that the application is communicating with the server. Capturing this DOM event in Silverlight is actually quite easy – when creating the IFrame I could just use the AttachEvent method on the HTML element, specifying the JavaScript event to capture and the .NET method to handle it. However we strike a problem in loading a PDF in the IFrame as for some reason the onLoad event is never raised (I assume it is because a separate plug-in takes over loading the page), though it is raised correctly for HTML pages. As our reports will be PDF files we won’t be able to use this feature in our application to indicate the fact that server communication is in progress, though the functionality has been included in this control for reuse purposes when displaying HTML pages. However for our purposes an alternative is required so that the user knows something is happening.
What I was trying to achieve as a substitute was to display a “Please wait – loading...” message in the control area. I discovered that if I set some HTML locally to display immediately in the IFrame and navigated the IFrame to the report URL on the server, then this would continue to display until the report had been retrieved from the server and the Adobe Reader plug-in had loaded itself and the report. So if I created some simple HTML showing the please wait message and set it as the content of the IFrame then this would display until the report had loaded.
I attempted to build up the HTML to display using the HTML Bridge functions to create the DOM for the page, however IE displayed this in quirks mode as there was no way to set the DOCTYPE for the loading page via the DOM – displaying the page differently to how it was displayed in Firefox and without the expected formatting. Therefore I needed to find an alternative means of creating and loading this page locally. I then discovered that a reference to the empty document created in the IFrame automatically when it is created can be obtained. We can then open it for direct HTML content writing, write the entire HTML document to be displayed (including the DOCTYPE, html, head, and body tags), and then close it. This document would then be displayed in the IFrame. The open, write, and close functions are not available via the HTML Bridge but can be called using the Invoke method on the document object.
Note that there appears to be a bug in Internet Explorer which I haven’t found a workaround for. Even after the report has loaded, the progress bar in the status bar continues to show. This appears to be due to a combination of writing of writing directly to the document in the IFrame then navigating to a different page – if I comment one or the other out the progress bar is hidden correctly, but using both together leaves it visible. This problem does not appear to occur in Firefox.
The final issue we need to find a solution for is how to know when to remove the IFrame from the DOM. Silverlight user controls don’t have a Closing or Closed event, the Dispose event when implementing IDisposable isn’t automatically called, and the destructor/finaliser being called is unreliable as it relies on the whims of the garbage collector as to when the control is cleaned up to be called. In any case we’ll implement the IDisposable interface and have this method remove the IFrame from the DOM. We can then call this from our parent user control, but again we hit another problem – our parent doesn’t know when it is being closed either! Since our underlying framework handles the navigation between “pages” and to enable this each “page” user control implements our IContentPage interface, we can add a Close method to this that the framework will call before it removes the user control from the visual tree to open another one. This is the method I chose, and from the Close method in the host/parent user control the Dispose method on the control can be called. As an additional feature to this, the Close method takes a new CloseArgs event arguments object as a parameter. This object has a Cancel property which if set to true will cancel the closing of the “page” user control and stop the next one from being opened.
Reports
As previously described, the reports will be generated using the local report viewer (ie. the engine that generates reports from templates created using the Visual Studio Report Designer) and rendered to PDF files. These will be streamed directly to the user’s browser and displayed in the HTML Viewer control. To enable this we’ll create an HTTP Handler in our web project to serve these reports to the Silverlight clients. Note that you will need to add a reference to the Microsoft.Reporting.WebForms DLL to your web project to enable reports to be generated.
I have created a base class to permit reusability of the HTTP Handler code to handle report requests in a generic fashion (BaseReportHandler). This class handles authenticating the user, finding the renderer for the report they have requested (soon to be discussed), checking if the user is authorised to view the report, requesting that the renderer handle the report generation, then streaming it back to the client as a PDF file. All that needs to be done is to create your own HTTP Handler, inherit from BaseReportHandler, and override the RegisterRenderers method. The purpose of this method is to populate the ReportRenderers dictionary from the base class, defining all the supported renderers with the key as their name and the value as a string containing their namespace and class name. An example is as follows:
protected override void RegisterRenderers()
{
ReportRenderers.Add("ProductDetails", "AWWeb.Reports.Products.ProductDetailsReportRenderer");
ReportRenderers.Add("ProductCatalog", "AWWeb.Reports.Products.ProductCatalogRenderer");
}
When a report is requested, the base class will then find the entry matching the ReportName query parameter from the HTTP request and instantiate the associated report renderer using reflection. Now I should explain what I mean by report renderer. This is a pattern that I have created to simplify the generation of reports. Often there are a few processes in generating a report (such as populating the report with data, populating subreports with data, etc) and properties to set (such as the report path, name, authorisation rights, orientation, size, etc) that are specific to that report. Rather than attempting to create an overly complex generic solution, what I do is offload responsibility for generating the report to its report renderer class. This class will handle all the creation and population of the report and return it back to the HTTP Handler as a LocalReport object ready for streaming back to the client. Of course there is a base class to help manage this workflow named BaseReportRenderer. Each report should have a renderer class that inherits from BaseReportRenderer and overrides the appropriate methods and properties. A simple workflow implemented in the GetReport function in the base class is to create a LocalReport instance, set the location to the report template, populate the report data sources, and return this LocalReport instance back to the HTTP Handler. If this is the required workflow we can just override the ReportPath property and the PopulateReportDataSources function in our report renderer (the minimum requirements for any report to be rendered). For more complex scenarios you can override the GetReport function to have full control over this workflow.
The PopulateReportDataSources function in our report renderer is where most of the work in the renderer is done. This function needs to obtain the required data for the report from the database and pass it to the LocalReport instance. The Report Viewer control can take in either DataTables or collections of objects as a data source. Ideally we’d like to go through our entity framework model to populate the report, and since I prefer working with objects over DataSets/DataTables I decided to go with the collections of objects method. For a collection to appear as a project data source in the Report Builder a class in the project needs to have a static method that returns a collection (such as a List or Array) of objects. You’ll note that we can’t simply select entities from our Entity Framework model when building a report because of this requirement for a static method. In any case, reports often require data combined from multiple entities so we should create our own data source class to populate the report with, much like the summary DTO classes that we pass back to the Silverlight application to populate lists. To enable this, we’ll create a simple class containing only properties to hold the data, and a static Get method to run the query on our Entity Framework model that returns a populated collection of itself. This class may be shared between multiple reports if they use the same data source and query, or it may be specific to just one report. The PopulateReportDataSources function in our report renderer can then call the static Get method and assign the results to the appropriate data source on the report.
We need to consider that the query that returns the data to populate the report may need to take some parameters to include in its where clause. These should be the parameters of the static Get function to be set by the report renderer, but some of these parameters will not be static values that can be hard coded into the renderer. Instead these values may differ between requests (such as a report that prints out the details of a product based upon the product ID of the product they select to print them for). In the base report renderer class I have a Parameters property (a Name Value Collection) that the base HTTP Handler will assign all the HTTP request query parameters to. The report renderer can then find the required parameters and their values from this to pass through to the static Get function of the data class.
To limit the users who are permitted access to a report you can override the IsUserAccessAuthorised property in the report renderer and determine based upon the user’s role whether they should be permitted access to the report. You may also wish to limit what data the user is permitted to view by their user ID or role (such as only allowing a sales person to view a list of their own sales). This should be implemented in the PopulateReportDataSources function as a part of the where clause in your LINQ to Entities query.
It’s important to implement security on your reports as they may contain the most sensitive business data which you would not want anyone to obtain access to. Another function security can provide is to filter the data in a report to contain only the data that that user has the rights to view whilst using a common report template (such as permitting a sales person to view their own sales figures but no one else’s). We have a dilemma though. Previously we have been passing the user credentials to the server in the SOAP message header of each WCF service call (see Part 3 for more details). However, when requesting a report we won’t be making a WCF service call – in fact we won’t be making a web request at all – the browser will be making the request itself! Therefore we lose control over the request and lose the ability to insert the credentials into it. This leaves us with two options to pass the credentials to the server – via query parameters in the url that requests the report, or by creating a cookie containing the details (which will automatically be included as a part of any request to the server). As the url for each page request is stored in the browser’s history and is visible in the address bar, using query parameters is out of the question. Therefore creating a cookie containing the details is the only reasonable solution. What I have done is create a short lived cookie (30 seconds lifetime) before each report request containing the user name and password for the user. Details on how this cookie was created can be found later in this article in the HTML DOM Interoperability section. The HTTP Handler that generates the reports will retrieve the cookie and validate the credentials and ensure the user is authorised to access that report.
To create the security cookies we need to use the HTML Bridge again. I wrapped some functions to work with cookies in a class (imaginatively titled Cookies), under the Utilities namespace/folder. To set a cookie using the HTML Bridge the following code is required:
string cookie = key + "=" + value + ";expires=" + expiryDate.ToUniversalTime().ToString("R");
HtmlPage.Document.SetProperty("cookie", cookie);
Despite having wrapped this in a reusable function, there are two noteworthy aspects that I thought I’d point out. As you can see, to set a cookie you set the value of the cookie property of the document. However this doesn’t overwrite the existing cookies, instead any cookie that exists with the same key will have its value and expiry updated, and if the key doesn’t exist then the cookie will be added to the collection. The other aspect to note is that when setting the cookie expiry the time needs to be converted to UTC time rather than the local time. Most examples I have seen in relation to writing cookies in Silverlight don’t include this, thus cookies may live for a longer (or shorter) period than would be expected. Although we aren’t currently using them I also added Get and Delete functions for cookies to the utility class.
Once again I will recommend securing your server communications with SSL. The user credentials are being passed to the server in plain text and using SSL is the only way to properly protect them. In a previous article I demonstrated how to configure the Silverlight application to be downloaded over a standard unencrypted http connection whilst using https to communicate with the server. This option could be configured on the web service configuration, and I have written the code such that requests for reports will honour this setting too (ie. if SSL is to be used for WCF service calls then requests for reports will be made via https too).
On the client side (in the Silverlight application) I have created a ReportRequest class (under the Reporting namespace/folder) that simplifies requesting a report from the server. I have included a function to build the url to the report generation HTTP Handler (including adding the query string parameters for the report), and another to set the temporary user credentials cookies.
To demonstrate the solution I have incorporated two reports into the application. You’ll notice in the inventory section of the application that there is now a “Product Catalog Report” menu item in the side bar. This example demonstrates implementing the HTML Viewer control to display a report that appears to be contained within the Silverlight application. This report was not one that I designed myself but was one that I obtained from the SQL Server Reporting Services examples on CodePlex. As previously mentioned the templates built using the Visual Studio Report Designer and those for SQL Server Reporting Services both use the same RDL (Report Definition Language) file format, and can almost be used interchangeably. The primary difference we need to worry about is that the database query is contained within the report to be run under SQL Server Reporting Services, whereas using the local engine we need to pass the data for the report into the report viewer engine. RDL files are XML based, so I opened it up in Notepad and copied out the query. I created a class containing matching properties with the same field names included in the query in my web project, and then wrote a LINQ to Entities query to query my Entity Framework model, pull back the same data as the query in the report would, and populate a collection of my new class. I opened the report in the Report Designer and changed the data source to reference the collection of my class (which wasn’t really necessary). Now when generating the report I can set the populated collection as the data source for the report and it will render the report as it would have under SQL Server Reporting Services.
The other example demonstrates enabling the user to print the details of a product. As previously described, there is no printing functionality within Silverlight, so to implement this feature we can display a report that the user can then print – it’s not an ideal or elegant solution but does work. On the product details screen I have added a “Printable” button to the toolbar. I investigated automating the Adobe Acrobat plug-in to automatically print the document but I wasn’t particularly happy with the possible solutions so have decided to omit this functionality. Therefore the PDF won’t be directly sent to the printer, but instead relies on the report being downloaded and displayed with the user manually selecting the Print option from the Acrobat Reader plug-in. As an additional manual step is required in this process to start the printing process I decided to name the button “Printable” rather than “Print” to help save any confusion. Using a report to enable details printing functionality such as this means that extra work is involved in development to produce a report template (as opposed to printing a HTML page), but should result in a better looking output.
So that the product details screen isn’t closed when the user wants to print the details I decided to open the report in a new (popup) window rather than using the HTML Viewer control within the application. This is a simple process using the HTML Bridge – the HtmlPage.PopupWindow function will pop up a new window with the specified options (such as the size of the window, whether the menu bar or scroll bars should be shown, etc), and will navigate to the specified url. Note that as the report opens in a popup window, the user may have popup windows disabled and may need to add your site to the list of sites permitted to open popup windows in order for the report window to open. I have wrapped this functionality up in the OpenReportInNewWindow function in the new ReportRequest class.
Note that there is another issue to be aware of. The report is generated based upon the data in the database – not as it is currently displayed on the screen. Therefore the user will need to save their changes before clicking the Print button in order for them to appear in the report. Currently I don’t currently check to make sure that all changes have been saved before displaying the report – thus any unsaved changes to the product will not appear and may create some confusion for the user. The two solutions to this are to either save the product details before generating and displaying the report, or check to see if the user has any unsaved changes and request they save them first before generating the report. The second solution is probably the best but I have not implemented it as yet.
An issue worth mentioning (as it has created confusion for me on a number of occasions) is that the Adobe Acrobat Reader plug-in sometimes starts to fail to load leaving you with just a white screen where the report should be. What I have found is required is you to open up Task Manager and end the AcroRd32.exe process. Then attempt to view the report again and the plug-in will load correctly.
When deploying the application you’ll need to install the runtime for the report engine which isn’t installed as a part of the .NET Framework. If you don’t have the Microsoft Report Viewer Redistributable 2008, you can download it from here.
Alternative Solutions
There are many third party reporting tools available – most of which can export reports to PDF and be used in the same manner described in this article. If you have a preferred reporting tool then it should be fairly easy to substitute the one I have used with your own.
On my soapbox for just one minute, please don’t create your own reporting tool unless you really need to! I expect a good amount of disagreement and controversy on this point, but I don’t believe writing a custom reporting tool provides good value for money for clients, and can add a huge maintenance burden to an application. It may be a fun project to work on, but it creates maintenance and support nightmares as there is (in my opinion) no such thing as a perfect reporting tool. I sincerely believe custom reporting tools are a recipe for a black hole in your project budget and timeline. There are many reporting tool packages out there, and while none of them are perfect they will tend to satisfy most client requirements at a reasonable cost. On a similar point, I have found that creating report templates almost always gets pushed back to the developer no matter how easy the report builder might be to use or how tech savvy the user might be – users tend to have better things to do with their time than design reports, so I don’t personally believe it’s viable to push report template creation away from developers. I do see value in enabling the user to modify the output after generating a report however, as when minor tweaks are required they can add an excessive burden on the support team. That’s why I tend towards favouring an output that the users can edit to suit their needs such as generating Word documents rather than PDFs, and when using the Open XML standard (DOCX) format introduced in Microsoft Word 2007 (with the Office Compatibility Pack downloadable from Microsoft to open these files in earlier versions of Word) it can be very easy to populate a Word document template with data on the server (without requiring messy automation). This may sound like what I was advocating against (building your own reporting tool) and to some extent it is, so I don’t recommend it in all cases. In some cases it may be better to move to a reporting engine that can generate DOC or DOCX as their output (such as scaling up to SQL Server Reporting Services).
Conclusion
We now have implemented a means for displaying reports that appear to be within the application and permitting them to be printed. This solution has its faults and is lacking in elegance in some aspects, but is still a viable and workable solution. Stay tuned for my next article which will cover styling the application to give it a professional look.
Resources
Limitations of Windowless Mode for Silverlight by Karl Erickson:
http://blogs.msdn.com/silverlight_sdk/archive/2008/11/12/limitations-of-windowless-mode-for-silverlight.aspx
Displaying HTML using Silverlight 2:
http://blogs.msdn.com/markda/archive/2008/05/21/displaying-html-using-silverlight-2.aspx
Silverlight HTML Bridge FAQ by Erik Reitan:
http://blogs.msdn.com/erikreitan/archive/2008/12/02/silverlight-html-bridge-faq.aspx