Showing posts with label Data Dynamics Reports. Show all posts
Showing posts with label Data Dynamics Reports. Show all posts

Monday, March 14, 2011

DD Reports - Advanced interactive charts, part 1

General
One of the amazing things about charts in Data Dynamics Reports is they may be interactive. You can check out chart drillthrough screencast to see this feature in action. But perhaps you need to adjust the way the charts show the subsequent data by displaying the popup window instead of displaying the drillthrough report in the same window? Yes, sir, Data Dynamics Reports allows to do this. In this article we will see how it can be achieved in the windows-forms based report viewer. The next article will give some ideas on how to implement this feature in the web-based report viewer. As usual let's formulate the problem and then solve it step by step.

The problem
DistrictSales.xml(change extension to rdlx) displays the pie chart of the sales by district. If a user clicks on the piece of pie, then the StoreSales.xml(change extension to rdlx) is opened and shows the detailed information about the sales by store in the corresponding district. Now we want to adjust this interactivity - if a user clicks on the piece of pie, then the popup window hosting the sales by store appears. For your convenience I created AdvancedChartDrillthrough.zip we can start with, implementing the requirements step by step. The rest of the article walks through these steps.

In action
(1)Open AdvancedChartDrillthrough project in Visual Studio 2008. Adjust the references to DDR assemblies if it's needed, compile and run it. The main form hosts the viewer that displays DistrictSales report with the default interactivity - clicking on the piece of the pie chart shows StoreSales report in the same window. We are about to adjust it.

(2)Open the code of MainForm.cs, add the handler of Action event of the viewer before the call to OpenReport method, like below:

public MainForm()
{
// the rest of the code
viewer.Action += viewer_Action;
viewer.OpenReport(new FileInfo("DistrictSales.rdlx"));
}
void viewer_Action(object sender, DataDynamics.Reports.ActionEventArgs e)
{
}

(3)Action event is raised when a user clicks an interactive element - hyperlink, drill through link or bookmark link. In the handler of Action method you can cancel the default processing and perform the custom steps, like opening the drillthrough reports in the popup window. Let's start with handling action event - we will extract the information about report to jump and set its parameters values:

void viewer_Action(object sender, ActionEventArgs e)
{
// we are interested in drill through actions only.
if(e.Action.ActionType==ActionType.DrillThrough)
{
// cancel the default processing
e.Cancel = true;
var drillthroughInformation = e.Action.Drillthrough;
// get the report to jump
ReportRuntime drillthroughReport = drillthroughInformation.ReportRuntime as ReportRuntime;
if(drillthroughReport == null)
throw new InvalidOperationException("unable to get the drillthrow report!");
// set the parameters if any
for(var i=0;i<drillthroughInformation.NumberOfParameters;i++)
{
drillthroughReport.Parameters[i].CurrentValue = drillthroughInformation.Parameters[i].Value;
}
}
}

(4) The remaining steps are trivial - we need to open the drillthrough report in the popup form. To do this let's adjust the code of PopupForm.cs - extract "viewer" field and add OpenReport method:

public partial class PopupForm : Form
{
private readonly ReportPreview viewer = null;
public PopupForm()
{
InitializeComponent();
viewer = new ReportPreview();
viewer.IsToolbarVisible = false;
viewer.PageBorder = PageBorder.None;
viewer.Dock = DockStyle.Fill;
viewer.PreviewMode = PreviewMode.Layout;
Controls.Add(viewer);
}
public void OpenReport(ReportRuntime reportRuntime)
{
viewer.OpenReport(reportRuntime, "sales by store");
}
}

and finally let's show the popup form in viewer_Action handler code(at the end of the method body):

void viewer_Action(object sender, ActionEventArgs e)
{
// the rest of the code
var popupForm = new PopupForm();
popupForm.Show();
popupForm.OpenReport(drillthroughReport);
}

(5)AdvancedChartDrillthrough_final.zip the final version of AdvancedChartDrillthrough project.


Next time
Interactivity involves the popup form on the web-based report viewer is going to be interesting to deal with!

Thursday, February 17, 2011

Data Dynamics Reports: One touch printing on the web

Preface
Some time ago I blogged around ideas on no-touch printing on the web in ActiveReports. A lot of the new features were added in both of ActiveReports and Data Dynamics Reports since from that blog post was published. Particularly we have updated PDF export capabilities so that they allow performing one touch printing easily. In this article I will explain it in details.

The problem
Let's imagine that we have the simple Html page. It hosts the list of the product categories(obtained from Northwind database, Categories table) and "Print" button. We need to implement the following functionality: a user selects the category, clicks Print, the server returns the PDF document that shows the list of the products from the selected category(obtained from Northwind database, Products table), PDF document starts printing immediately - a user should get the print dialog of PDF viewer and the document should not be opened in the viewer.

One-touch printing in action
Let's create the web-site that implements the functionality described in "Problem" section:
products.xml(change the extension to Rdlx) is the report that accepts category id parameter and displays the list of the products accordingly. Copy products.rdlx in the root folder of the web-site.
Now copy default.htm in the root folder of the web-site and let's look at the page body:

<body>
<label>Select the category to print the list of the product of:</label><br />
<select id="categoryList">
<option value="1">Beverages</option>
<option value="2">Condiments</option>
<option value="3">Confections</option>
<option value="4">Dairy Products</option>
<option value="5">Grains/Cereals</option>
<option value="6">GMeat/Poultry</option>
<option value="7">Produce</option>
<option value="8">Seafood</option>
</select>
<input id="btnPrint" type="button" value="Print" />
<script>
$("#btnPrint").click(function() {
var catId = $("#categoryList").val();
$("#pdfContentPlaceHolder").attr("src", "GetData.aspx?catID="+catId);
});
</script>
<iframe style="visibility:hidden" id="pdfContentPlaceHolder" width="0" height="0" />
</body>

So, in the handler of of Print button click we set the source of the invisible frame to the data that are returned by GetData.aspx page. Once the document is loaded in the frame, the printing should start immediately. How is it achieved? Copy GetData.aspx in the root folder of the web-site and let's look at its Page_Load code:

void Page_Load(object sender, EventArgs e)
{
int catId;
if(!int.TryParse(Request.QueryString["catId"], out catId))
{
Response.Write("Invalid category Id parametere value! No output you will get");
return;
}
var fi = new FileInfo(Server.MapPath("~/Products.rdlx"));
var def = new ReportDefinition(fi);
var runtime = new ReportRuntime(def);
runtime.Parameters[0].CurrentValue = catId;
var pdf = new PdfRenderingExtension();
var streams = new MemoryStreamProvider();
var settings = (pdf as IConfigurable).GetSupportedSettings().GetSettings();
settings["PrintOnOpen"] = "true";
runtime.Render(pdf, streams, settings);
Response.ContentType = "application/pdf";
Response.AddHeader("content-disposition", "inline; filename=products.pdf");
using(var stream = streams.GetPrimaryStream().OpenStream())
{
var len = (int)stream.Length;
var bytes = new byte[len];
stream.Read(bytes, 0, len);
Response.BinaryWrite(bytes);
}
}

The code is quite trivial, there are two relatively new features in use though:

  • MemoryStreamProvider allows to export the reports to the memory instead of the disk files. It is pretty convenient for web-scenarios where you don't want to use file system aggressively.

  • We set PrintOnOpen setting of PDF export to true. It will force PDF document to start printing immediately once it is loaded in the invisible frame on default.htm

The task is completed.


ActiveReports
It is possible to use the same technology in ActiveReports. The setting for PDF export filter document option is called OnlyForPrint, it is hidden in the intellisence, but you can use it:

PdfExport pdf = new PdfExport();
pdf.PdfDocumentOptions.OnlyForPrint=true;

Tuesday, February 8, 2011

DDR: Using data visualizers to build a thermometer chart

Preface
Data Dynamics Reports does not include circular gauge and meters controls. We did not add them with good reason - they tend to be misleading and and take a lot of space on the screen. Instead we provide Bullet and Sparkline graphs that can visualize data quite effectively. However, as one of our customers rightfully mentioned, the sales folks adore meters controls - speedometer, thermometer, etc. and it would be good to have these controls in the product. This article shows how to compose a thermometer chart by using wonderful built-in feature called "DataBar visualizer".

The problem
Let's imagine that we are about to build the patients temperature report that is sent to chief of a hospital department every morning. The report shows the table that consists of two columns: patient name and patient temperature that was measured at 6am. The chief demands that the temperature value is visualized by a gauge, like this one:

The thermometer has 3 ranges:

  • [0;35] brushed with blue color - patient has problems or already dead.

  • [35;37] brushed with green color - patient is feeling well!

  • [37;43] brushed with red color - patient has fever or about to die.

The yellow marker visualizes the temperature value.


DataBar visualizer in action.
The rest of the article walks through the process of building the report. For convenience sake the walkthrough starts from the existing report layout that only includes the data are set.

  • Open PatientsTemperature.xml(change .xml to .rdlx) by using either DataDynamics.Reports.Designer.exe application or Visual Studio.

  • Add the table report item in the report body. Remove the 3rd column and the footer of the table. Set table's DataSetName property to "Temperature" . Set the the 1st column, details row cell value to "=[Patient]"

  • Add the Rectangle Report Item in the table's 2nd column, details row cell. Set the rectangle's background color to Green and the name to "rectOuter". This rectangle will show the 2nd range of the thermometer.

  • Add the nested rectangle in rectOuter. Set the rectangle's name to "rectNested", the location to (0in,0in), the size to the same value as rectOuter has. Now the magic begins.

  • Set the properties of the rectNested BackgroundImage: Source:DataBase, Value:=DataBar(35,0,43,0,'Blue'), MIMEType:image/png, BackgroundRepeat:NoRepeat. What happens here? It's not allowed to set the rectangle's background image by using the data visulalizer editor, like for a textbox, but in fact the rectangle supports data visualizers! What do the DataBar arguments mean? 35 is the boundary of the 1st range, 0 is the minimum temperature value, 43 is maximum temperature value, 0 is the zero coordinate, Blue is the color for the 1st range according to the requirements. The documentation describes the arguments meaning in details. Now preview the report. Can you see how the magic works? The rectNested's background image takes the space according to DataBar Value argument. The rest of rectNested is transparent which makes rectOuter's background partially visible. Now our thermomemeter shows 2 ranges. Fantastic!

  • Add the nested rectangle in rectNested. Set rectangle's name to "rectNestedNested", location to 0in,0in), size to the same value as rectOuter and rectNested have. As you might deduce rectNestedNested will show the 3rd Red range of our thermometer. Let's set it's background image accordingly:

  • Source:DataBase, Value:=DataBar(43,0,43,37,"Red"), MIMEType:image/png, BackgroundRepeat:NoRepeat. In DataBar function we set the zero coordinate to 37 so that the red background starts from the 2nd range right boundary. The magic described in (5) works for the 3rd range as well.

  • Now let's add the temperature value marker. It is done by using...drumroll please...rectangle that has DataBar invoke in background image expressions. Add the nested rectangle in rectNestedNested. Set the following properties for this newly added rectangle: Location.Left=0, Size.Width=the same value as the parents rectangles have. Set Location.Top and Size.Height to the appropriate values for the marker. Source:DataBase, Value:=DataBar(Fields!Temperature.Value,0,43,0,"Yellow"), MIMEType:image/png, BackgroundRepeat:NoRepeat.

  • Now select rectOuter, rectNested, rectNestedNested and set their rounding radius property to some value, i.e. 0.1in. It will make our pseudo-thermometer look closer to what was shown in the picture above. PatientsTemperature_Final.xml(change .xml to .rdlx) is the sample of the final report version and here is the output sample


is this picture informative? Yep, 3 of our 5 patients have fever and it seems that we forgot to move Mr. Jonathan Murraiin to morgue..Whoops!


Other scenarios
By using the same technique you can emulate bullet chart that uses traffic lights colors as described here.

DDR Excel Export: rendering chart report item as Excel chart object by using the Power of Templates

Preface
Excel Export in Data Dynamics Reports uses the amazing technology called “Templates” behind the scene. When a report is exported in Excel, we first build the intermediate XLS document that contains the placeholders for the actual data. This intermediate document is called “Template”.
Then we obtain the data that report normally shows and insert them in the template. The attentive reader will ask - So what? Why is this technology amazing? The answer is we expose the template to the real world: it is possible to obtain the template, modify it and pass to Excel Export. As the result the output may be tuned by creating the custom data placeholders: formulas, pivot tables and so on. In this article we will see how to solve on of the common problems with Excel export by using the Power of Templates.


The problem: chart output in Excel Export
If a report contains chart report item, then it is shown as the image in the exported XLS Document. It is not comfortable - Excel has the great charts support and it would be nice to see the DDR chart shown as Excel Chart in the output. Let’s look at hypothetical example. The application displays the attached DelayReport.rdlx(change xml to rdlx) by using the windows forms viewer and allows users to export in Excel(they just click “XLS” button and the resulting document is opened once exportation is done). The report consists of the table and the pie chart that show the data of employees delay statistic. By default the chart is transformed to the picture. The application users are not happy. They demand the Excel Pie Chart in the output. Let’s see what you can do step by step.

Templates power in action

  • Open the attached DelayReport.rdlx in the report designer - either in standalone application or visual studio integrated editor.

  • Select Report->Generate Excel Template menu item. Save the template in the appropriate location. Open the generated template in Excel.

  • The cell contains “<#Chart1#>” text is the placeholder where the chart picture is going to be inserted, so remove this text, unmerge the cell and decrease its height if it’s needed.

  • The cell contains “<#txtReason#>” text is the placeholder for “Reason” column of the table. The values from Reason column will be transformed to the range of cells. This range will start from “<#txtReason#>” cell position and grow further. Let’s name this range: select “<#txtReason#>” cell and define the new named range - in Excel 2007/2010 you can go to Formulas tab, click “Define name”, leave the default values in the popup dialog and click OK.

  • The cell contains “<#txtCasesNo#>” text is the placeholder for “CasesNo” column of the table. Name the range it will produce by using the same way as in (4).

  • Add the Pie chart on the appropriate place(Excel 2007/2010 - Insert tab, Pie chart). Right click on the chart, choose “Select Data...” and remove the default Legend Entry(Series) value.

  • Click Add on Legend Entries(Series) editor. Set Series Name to “Delays”. This will be chart title which equals to one in the original report. Then set Series Values to “=Sheet1.txtCasesNo”. This way you map the chart data to the range created on step (5).

  • Click Edit on Horizontal (Category) Axis Labels. Set labels range to “=Sheet1!txtReason”. This way you map the chart labels to the range created on step (4).

  • Note that the chart data range is set automatically. Click OK. Save the template. The attached template.xls is the sample of the described modifications result.

  • Test the exportation with the modified template. For example open the report by using DataDynamics.Reports.Preview.exe, choose Export->Excel in the toolbar. Set Template property in the popup dialog to the template path, select the output file name, click export. The attached output.xls shows the example of the output.

  • Now in your hypothetical application adjust the code of “XLS” button click handler so that the customized template is used for exportation. Use TemplateStream property.

The tip of the iceberg
What is described here is just the small piece of the great functionality that is provided by Excel Export Templates. Basically you can tune the output with any available Excel feature.

Sunday, June 6, 2010

Data Dynamics Reports And Twitter

Preface
Tips and tricks series continues! In this post I am going to explain how a Data Dynamics Reports report might be set up in order to show the tweets from arbitrary user's public timeline. The live demo is available here. I can imagine the real-world usage of this feature: say I am HR department manager and in the CV overview report I would like to see the last 3 tweets from the public timeline of the candidates for opened job position. Let's see how a developer can implement such a feature using Data Dynamics Reports.

Using XML Data Provider
Data Dynamics Reports includes XML data provider that can load the data from various sources, particularly it supports xml documents that are located in a web-accessible location. In other hand a user's public timeline can be accessed via RSS feed which is xml document that is located in a web-accessible location. I.e. my public timeline can be read on http://twitter.com/statuses/user_timeline/sAbakumoff.rss. RSS feed document has well known structure: each tweet is presented by <item> tag, its child <description> tag has the tweet text. So...displaying user's public timeline in Data Dynamics Report is pretty simple:
1) Add new Data Source in a report. Set Data Source type to XML.
2) Set the connection string of the newly added data source to "xmldoc=http://twitter.com/statuses/user_timeline/USER NAME.rss"(without quotes). Change USER_NAME to the real-world value.
3) Add the new Data Set in the Data Source.
4) Set the Query of the newly added Data Set to "/rss/channel/item"(without quotes).
5) Add the Fields to the newly added data set. Set the name of the field to "title", set the value of the field to "title".
6) Add the List report item in the report, set the DataSetName to the name of the data set that is added in step 3.
7) Drag the field "title" from the data explorer to the newly added List Report Item.
8) Preview the report, hey presto, it shows the user's public timeline!


Advanced techniques
I've used a couple of other features in the live demo:
1) To limit the tweets list for 5 last messages, I used TopN filter of the data set. I've added one more field to the data set, it's pubDate. The filter expression is "=Fields!pubDate.Value", the filter operator is TopN, the filter value is 5.
2) I parametrized data source query to allow set user name using the report parameter value, the connection string is set to
="xmldoc=http://twitter.com/statuses/user_timeline/" & Parameters!TwitterUser.Value & ".rss"
The complete report is available here.

Monday, May 24, 2010

Data Dynamics Reports: Customizing web-viewer toolbar

Preface
In this post I would like to share the tips and tricks for Data Dynamics Reports Web-based report viewer(hereafter referred to as "Web-Viewer") customization. Web-Viewer component does provide a developer with customization capabilities, but the are a couple of the problems with them - they are tricky to implement and not documented. I hope that this post partly solves those problems.

Web-Viewer overview
Web-Viewer is the ASP.NET server control that renders the HTML content in a client side. The HTML content consists of several logical parts - the toolbar, the side panel, the page viewer and the controller. The controller is the javascript object has the methods and properties that allow to load the report to display, navigate through the pages of a report, print a report, export a report to one of the supported formats, etc. All those actions are performed via AJAX requests/responses and updating the DOM of the HTML content at run-time.

Toolbar customization
This post is intended to demonstrate how a developer can make-use of home-grown toolbar for Web-Viewer. The implementation can be split to the following steps:
  1. Hiding the built-in toolbar
  2. Get access to the methods and properties of controller object
  3. Determine when a report page is loaded to update the custom toolbar
I will go through all those steps. However I need to clarify the code ammunition that will be used for the live demo. It is available here, the complete source code can be obtained here. I tested the live demo on IE8, FireFox 3.6.3 and the last version of Google Chrome. The demo works best on Chrome :)


JQuery and LiveQuery
JQuery is essential choice for a web-developer. I used JQuery selectors, element manipulations, the UI button element to build the live demo. That is expected. However, there is the problem that can't be solved by using the default JQuery library. Web-Viewer component can be added into an aspx page by writing something like <dd:WebReportViewer ID="WebReportViewer1" ReportName="~/Report.rdlx" /> Once an aspx page is requested, web-viewer component starts rendering of the HTML content that has the toolbar, side panel, etc. None of those element exists at design time when a developer builds an aspx page. Therefore the code needs to access to the elements that will be added at-runtime by updating the DOM of the HTML document! Fortunately there is nice Live Query plugin that "utilizes the power of jQuery selectors by binding events or firing callbacks for matched elements auto-magically, even after the page has been loaded and the DOM updated". This is exactly what we need. To operate on non-existing element a developer writes something like $('#ID').livequery(function(){/*do something*/}); and the code in the brackets executes once the element with id=ID appears on the DOM.

Hiding the built-in toolbar
The built-in Web-Viewer toolbar is the HTML element that has ID="reportViewerToolbar{Web-Viewer componentID}$coreViewer". For example if the ID of Web-Viewer component on the aspx page is "WebReportViewer1" then the ID of the toolbar will be "reportViewerToolbarWebReportViewer1$coreViewer". To hide the built-in toolbar, we can use the following code:

$('#reportViewerToolbarWebReportViewer1\\$coreViewer').livequery(function () {
  $(this).css('display', 'none');
});
So, when Web-Viewer component will render the built-in toolbar, this magic code will update its style with "display:none", and the toolbar will disappear from the page.


Get access to the methods and properties of controller object
Web-viewer component renders the script code that creates the instance of the controller class. Once this code is executed, the instance is available for the rest HTML page code. The name of the instance is "controller{Web-Viewer componentID}$coreViewer". For example if the ID of Web-Viewer component on the aspx page is "WebReportViewer1" then the name of the controller instance will be "controllerWebReportViewer1$coreViewer". Here is the table of the methods and properties of the controller class. A developer can make use of them on the script code:

MethodAction
nextPage()goes to the next page of a report
previousPage()goes to the previous page of a report
firstPage()goes to the first page of a report
lastPage()goes to the last page of a report
toggleSideBar()Shows or hides the side panel
print()Prints the report
openPageSetupDialog()Opens the dialog allows to set up the page properties
toggleRenderMode()Switched the preview mode to Galley or Print Preview
backToParent()Returns to the parent of the drillthrough report
openEmailDialog()Displays the dialog that allows to send a report via email
showDocMapSideBar()Opens the TOC tab of the side panel.
showFindSideBar()Opens the Search tab of the side panel.
showParameterSideBar()Opens the Parameters tab of the side panel.

For example, to create the Next Page button of my custom toolbar I can use the following code:

$('#btnNextJ').button().click(
function () { controllerWebReportViewer1$coreViewer.nextPage(); }
);
Also, controller instance has report property. The type of this property is the class that represents the currently displayed report. The report object also exposes useful methods and properties:
Method,PropertyAction
export(format)Exports report to the certain format. Possible parameter values are: PDF, Word, Xml, Mhtml, Excel
pageCountgets the number of pages in a report
pageNumbergets the number of the currently displayed page


For example, to create the Export to PDF button of my custom toolbar I can use the following code:

$('#btnExportPDF').button().click(
function () { controllerWebReportViewer1$coreViewer.report.exportReport("PDF"); }
);
See the complete sample of code on the live demo source page.


Determine when a report page is loaded to update the custom toolbar
This is the most tricky part of the implementation. The problem is the controller instance and its report property value are not initialized until the AJAX request for a report is completed. There is no well-known way to catch this moment. We need to use a work-around this issue. In HTML mode of Web-Viewer the report pages are displayed inside the frame element has ID="CorePanel{Web-Viewer componentID}$coreViewer". For example if the ID of Web-Viewer component on the aspx page is "WebReportViewer1" then the ID of the frame will be "CorePanelWebReportViewer1$coreViewer". JQuery allows to catch the moment when the frame is loaded using load function. In the live demo I use this trick to update the toolbar - to enable the buttons that are disabled by default, to set page number and page count text:
$('iframe[id="CorePanelWebReportViewer1\\$coreViewer"]').livequery(function () {
$(this).load(function () {
updateToolbar();
});
});
See the code of updateToolbar function on the live demo source page.


Behind the discussed topics.
All the tips and tricks I talked about this post work for HTML mode of Web-Viewer. I did not test them for PDF mode, however I am confident that all but the catch of report loaded event works in PDF. I did not try to customize the search, toc and parameters tabs of the side panel, but it might be done using the same techniques as for toolbar customizing.

Sunday, April 11, 2010

Tips and tricks: Exporting to CSV using Data Dynamics Reports

General
This is the first in a series of posts that will highlight small and simple but useful undocumented features of Data Dynamics Reports and ActiveReports. I have several topics in mind and hope that more ideas about such features come soon. I will start with the answer to question we were recently asked - is that possible to export the report to CSV format using Data Dynamics Reports?

Tip: use Xml Rendering Extension to get CSV output.
That sounds weird, but it's really not. Let me explain. Data Dynamics Reports allows you to save the data that are shown by a report to XML format. The output XML content does not include the information about report items style, pagination, interactivity, etc., it only includes the data that are grouped, sorted, filtered and displayed in the report's data regions. For example say that a report consists of a simple table that has two columns, the header and the details:

The report output in the viewer would look like below:

But the output that is produced by Xml Rendering Extension looks like:

<Report Name="Ages.rdlx">
<Table1>
<TextBox2>Age</TextBox2>
<TextBox1>Name</TextBox1>
<Detail_Collection>
<Detail>
<TextBox5>30</TextBox5>
<TextBox4>Jon</TextBox4>
</Detail>
<Detail>
<TextBox5>27</TextBox5>
<TextBox4>Michael</TextBox4>
</Detail>
<Detail>
<TextBox5>32</TextBox5>
<TextBox4>Bryan</TextBox4>
</Detail>
<Detail>
<TextBox5>29</TextBox5>
<TextBox4>Stewart</TextBox4>
</Detail>
</Detail_Collection>
</Table1>
</Report>

The element names in the output XML file can be fine-tuned using the properties on the individual report items, more details can be found on the corresponding help topic. That's all good, but how to get the same data in CSV format? For example:
Name,Age
Jon,30
Michael,27
Bryan,32
Stewart,29
Xml Rendering Extension has XsltStyleSheet setting, you can specify the stylesheet that will be applied to the output XML before producing the resulting document. Therefore you can write the XSLT that transforms XML content to CSV content. It's of course not possible to implement the uniform XSLT for all the reports, but the example for the table-based report where the table has the 1-row header and 1-row details is shown below. It works for any number of columns:


<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!--Select the table header content -->
<xsl:for-each select="Report/Table1/*">
<xsl:if test="name(current())!='Detail_Collection'">
<xsl:if test="not(position() = 1)">,</xsl:if><xsl:value-of select="current()" />
</xsl:if>
</xsl:for-each>
<!--Insert the line break-->
<xsl:text>
</xsl:text>

<!--select the table details content -->
<xsl:for-each select="Report/Table1/Detail_Collection/Detail">
<xsl:for-each select="current()/*">
<xsl:if test="not(position() = 1)">,</xsl:if><xsl:value-of select="current()" />
</xsl:for-each>
<!--Insert the line break-->
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Sunday, March 14, 2010

Data Dynamics Reports: Show the Data from the Windows Azure Table Storage

Preface
Some time ago I tested Data Dynamics Reports on Windows Azure platform. I never dealt with Windows Azure before and was happy to learn something new. In particular I've learned the basics of Windows Azure Storage Services. There are 3 storage types in Windows Azure: blobs, queues and tables. I thought about the scenario when a developer wants to visualize the data from Window Azure Table Storage using Data Dynamics Reports. I've found that Data Dynamics Report provides developer with all needed ammunition to achieve that goal, so I thought that it would be worth to write about.

Windows Azure Table Storage
Windows Azure Table Storage differs from the tables in a relational database. Table storage is object-oriented and saves the entities and the entity properties. The details of table storage is not the subject of this post. I believe that the best way to show how Data Dynamics Reports can be used with the table storage is going through the live example. This walkthrough is based on the post in "Cloudy in Seattle" blog. However, that blog post sample uses Windows Azure SDK 1.0 and I used the newer version 1.1. A few things was changed in version 1.1, I highlighted them later in this post.

Walkthrough
This walkthrough is totally based on "Windows Azure Walkthrough: Table Storage". I just aligned it with the changes in Windows Azure SDK v1.1 and added the steps for show the data using Data Dynamics Reports.
(1) Get started with Windows Azure - install the prerequisites and SDK v1.1. Install the latest release of Data Dynamics Reports.
(2) Download and extract Windows Azure Additional Samples from MSDN code gallery.
(3) In Visual Studio 2008 or 2010 create the new Windows Azure Cloud Service project.
(4) In the pop-up dialog add C# ASP.NET Web Role into the Cloud Service Solution.
(5) Compile StorageClient solution that is included in the advanced samples obtained on step (2). From the Web Role add the reference to StorageClient assembly.
(alternatively you can include the *.cs files from StorageClient solution to the Web Role project. F.e. I've added them as a link in my test project).
(6) Add the reference to System.Data.Services.Client in Web Role project.
Steps (7)-(23) are the same as in Table Storage Walkthrough. However step 22(Create Test Storage Tables) is not needed with Windows Azure SDK 1.1.
(24) Add several contacts using the form on the default page of the cloud service. We will show the contacts list using Data Dynamics Reports.
(25) Add the new Data Dynamics Reports Report Item in the Web Role Project(in the root folder). Name it "Contacts.rdlx". Set Build Action property of Contacts.rdlx file to "Content".
(26) Using the Report Data Explorer, add the new Data Source in the report. On the General Page set the Data Source Type to Object Provider.
(27) Add the new Data Set to the Data Source that is created on step (26). Leave the data set query empty. On the Fields Page of the Data Set add two fields "Name" and "Address".
(28) Add the Table to the report, bind the table to the Data Set that is created on step (27). Adjust the table so that it contains 2 columns and the 1st column shows Name field and the 2nd column shows Address field.
(29) Add the new Web Form in the Web Role Project. Name the new form "ReportForm.aspx".
(30) Add Data Dynamics Reports Web Viewer to ReportForm page.
(31) Add the following code to the Page_Load event handler of ReportForm:

const string rptPath = "~/Contacts.rdlx";
string rptPath2 = Server.MapPath(rptPath);
var def = new ReportDefinition(new FileInfo(rptPath2));
var runtime = new ReportRuntime(def);
runtime.LocateDataSource += (s, args) =>
{
   var ds = new ContactDataSource();
   args.Data = ds.Select();
};
WebReportViewer1.SetReport(runtime);

So, we used Object Data Provider for the report data source. Object Data Provider accepts the objects that implement IEnumerable interface. ContactDataSource.Select method returns the results of the query to the TableStorage as IEnumerable<ContactModel> instance and we can use it for the report data source.
(32) Set ReportForm.aspx as the Start Page, run the cloud service and you will see that web-viewer shows the contacts that you added on step (24).
End of walkthrough.

Sunday, February 14, 2010

Interaction between the Report and the Hosting application, Email Rendering Extension and more

Preface
I was pretty busy during past week and did not get the good ideas for the new post. Therefore I am going to write about two fairly new features of Data Dynamics Reports. They are Action event of Windows Forms Viewer and WebViewer and Email Rendering Extension. They are not well-documented yet, therefore this post will be probably helpful for someone.

Action event
Data Dynamics Reports has the great interactivity capabilities. It supports the hyperlinks, drill-through links, drill-down links, interactive sorting, bookmarks and the document maps. However, sometimes developer needs a custom type of interactivity. For instance - the report shows the list of employees, if a user clicks on the employee name in the report, then e-mail of employee is added in the internal list in the hosting application. A user selects a bunch of employees, closes the report and clicks "Send" button in the hosting application. The e-mail with the subject "You are fired" is sent to each employee from the list. Some time ago, it was not possible to implement such scenario using Data Dynamics Reports. But the fabulous product team went ahead and implemented the new feature that is called "Action event". Windows Forms Report Viewer and Web Report Viewer now have Action event. It is raised when a user clicks on the hyperlink, bookmark link or drill-through link in the report that is displayed in the viewer. In the handler of Action event developer can perform the custom steps and cancel the further action processing. Here is the sample of code:

public partial class _Default : System.Web.UI.Page
{
   internal List<string> _emailsList = new List<string>();

   protected void Page_Load(object sender, EventArgs e)
   {
      WebReportViewer1.Action += WebReportViewer1_Action;
   }

   void WebReportViewer1_Action(object sender, ActionEventArgs e)
   {
      if(e.Action.ActionType == ActionType.HyperLink)
      {
          _emailsList.Add(e.Action.HyperLink);
         e.Cancel = true;
      }
       e.Cancel = false;
   }
}

ActionEventArgs instance that is passed in Action event handler contains all the needed information: the type of Action and the value of the hyperlink, drill-through link or bookmark link. The most amazing fact about Action event is the uniform way it is implemented. It works for Windows Forms Viewer, Web Report Viewer-HTML mode and Web Report Viewer-PDF mode. In all cases developer just needs to add the handler for Action event.

Email Rendering Extension
Email Rendering Extension is the new rendering engine that allows to generate the report output which is compatible with the most popular e-mail clients. The clients with which we tested Email Rendering Extensions are:

  • Outlook 2007

  • Outlook 2003

  • Gmail

  • Yahoo

  • Hotmail

  • Thunderbird


Email Rendering Extension supports two modes - it can write the output to the file or memory stream and it can build the instance of System.Net.Mail.MailMessage which is available via the pubic property. The MailMessage instance that is generated by Email Rendering Extension contains the html view of the message and all the attachments, i.e. images, if any. Here is the sample of the code which sends the report output to the list of recipients, it continues the code that is shown above:


public partial class _Default : System.Web.UI.Page
{
   internal List<string> _emailsList = new List<string>();

   protected void btnSend_Click(object sender, EventArgs e)
   {
      var re = new EmailRenderingExtension();
      var def = new ReportDefinition(new FileInfo(Server.MapPath("~/Email.rdlx")));
      var runtime = new ReportRuntime(def);
      runtime.Render(re, null);
      var emailMessage = re.MailMessage;
      emailMessage.Subject = "You are fired";
      foreach(string email in _emailsList)
         emailMessage.To.Add(email);
      var fromAddress = new MailAddress("boss@company.com","Big Boss");
      emailMessage.Sender = fromAddress;
      emailMessage.From = fromAddress;
      var smtp = new SmtpClient("127.0.0.1", 25);
      smtp.Send(emailMessage);
   }
}

In this code Email Rendering Extension does not render the report to the file or memory stream. MailMessage property is used instead.