Sunday, November 7, 2010

Async+ActiveReports=AsyncActiveReporting

Introduction
Autumn is always welcomed as the season of great events in developers world. Autumn of 2010 shed light on the future of C# and VB.NET. Asynchrony support in C# 5.0 is what widely discussed nowadays. I would like to make a contribution as well. Let's look at some real-world scenario that makes use of ActiveReports and analyze how the new C# 5.0 features can help in the implementation.

The problem
Imagine that you build a WPF application. The UI includes the window with single button "Open Annual Report as PDF". If a user clicks on this button, then of course the annual report which is built by using ActiveReports is exported to PDF and the resulting PDF file is opened in the default PDF viewer that is installed on the system. Here is the XAML, I removed the attributes that don't matter for the discussion:

<Window>
<Grid>
<Button x:Name="btnExport" Click="btnExport_Click" >
Open Annual Report as PDF</Button>
</Grid>
</Window>

Here is the code that performs exportation:

private string ExportReportToPdf(string rpxPath)
{
var arReport = new ActiveReport();
var reader = new XmlTextReader(rpxPath);
arReport.LoadLayout(reader);
arReport.Run(false);
var pdfExport = new PdfExport();
var outFileName = System.IO.Path.GetTempFileName() + ".pdf";
pdfExport.Export(arReport.Document, outFileName);
return outFileName;
}

And the button click handler:

private void btnExport_Click(object sender, RoutedEventArgs e)
{
string pdfFileName = ExportReportToPdf(@"C:\AnnualReport.rpx");
Process.Start(pdfFileName);
}

This code has the obvious drawback. If the annual report is huge, then its running and export may take a long time. Let's say that the report is SO huge that it takes 1 minute to run and export to PDF(it's hard to imagine though because ActiveReports is super-fast!). Our ExportReportToPdf works in the UI thread and it does not return the control to the caller. It means that UI is not responsive while ExportReportToPdf does its job. This is not acceptable at all. The UI should be responsive. Moreover, it would be nice to allow end-user to cancel the operation and provide some kind of progress report. Indeed .NET framework has all the ammunition that is needed to solve these problems. For example BackgroundWorker usage example is very similar to what I described above. However, MS realized that using the techniques such as BackgroundWorker is painful - they make use of the background thread, they require quite convoluted code for the cancellation and progress reporting support, they require extra work for updating UI safely and so on. Let's see how we can make UI responsive, provide cancellation and progress reporting support by using the new C# 5.0 features.

All you need is Await and Async
Now let's rewrite the button click handler by using the new C# 5.0 async and await features. Async and Await is not magic. It's the syntax sugar for continuation passing style implementation. There are nice blogs that describe how async and await work in details. This post just shows how they can be used in conjunction with the real-world scenario that makes use of ActiveReports. So, here is the async version of the button's click handler:

async private void btnExportClickAsync(object sender, RoutedEventArgs e)
{
var task = TaskEx.Run(() => ExportReportToPdf(@"c:\AnnualReport.rpx"));
await task;
Process.Start(task.Result);
}

And now the UI is responsive while the annual report is run and exported! Doesn't it look much easier to implement than, for example, Asynchronous Design Pattern?

What about cancellation?
Now let's add the support for cancellation of report running. In C# 5.0 the long-running task, such as report running or exportation should take care about cancellation. The task is provided with the object that reports whether the cancellation happened. The cancellation may be forced by the timeout or by the explicit invocation of Cancel method of the special object. For example let's add Cancel button to our UI and implement the cancellation support in our long-running task. Here is the updated XAML:

<Window Title="Pending..." >
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Grid.Row="0" x:Name="btnExport" Click="btnExportClickAsync">
Export report to PDF</Button>
<Button Grid.Column="1" Grid.Row="0" x:Name="btnCancel" Click="btnCancel_Click"
IsEnabled="false">Cancel</Button>
</Grid>
</Window>

So, we've added the window title that will indicate the state of the task. It may be "pending", "in progress", "done", "canceled". Those are not standard statuses but rather human-friendly statuses names which I use for the sake of clarity. Also, Cancel button was added and it is disabled when the application starts. There is no anything to cancel before task is run, isn't it?
Now let's look at the updated code of the Export button click handler:

private CancellationTokenSource cancelationObject;
async private void btnExportClickAsync(object sender, RoutedEventArgs e)
{
cancelationObject = new CancellationTokenSource();
try
{
this.Title = "In progress...";
btnExport.IsEnabled = false;
btnCancel.IsEnabled = true;
var task = TaskEx.Run(() => ExportReportToPdf(@"C:\AnnualReport.rpx"));
await task;
this.Title = "Done";
Process.Start(task.Result);
}
catch (OperationCanceledException)
{
this.Title = "Canceled";
}
finally
{
cancelationObject = null;
btnExport.IsEnabled = true;
btnCancel.IsEnabled = false;
}
}

So, we initialize and pass the "cancellation object" in our long-running task(through the class field) and catch the OperationCanceledException. If it is thrown, then the task was canceled. But who performs the actual cancellation? As I mentioned, In C# 5.0 the long-running task, such as report running or exportation should take care about cancellation. The idea: let's intercept the PageStart event of ActiveReport object and cancel the report running if it is needed. We need to update the code of ExportReportToPdf method:

private string ExportReportToPdf(string rpxPath)
{
var arReport = new ActiveReport();
var reader = new XmlTextReader(rpxPath);
arReport.LoadLayout(reader);
arReport.PageStart += (s, a) => cancelationObject.Token.ThrowIfCancellationRequested();
// the rest of the code
}

The last think to get cancellation working. Remember about Cancel button? Let's make it do its job!:

private void btnCancel_Click(object sender, RoutedEventArgs e)
{
cancelationObject.Cancel();
}

So, the entire picture of cancellation support is: The UI is responsive while the report running and exportation does its job. The user may click Cancel button and it tells "cancel the task!" to the special object. The report running intercepts the new page start event and tells "throw the cancellation exception if you told to do that!" to the special object. The task running catches the "cancellation exception" and updates the UI accordingly. The only drawback here is we can't cancel the task when the exportation starts(PdfExport.Export method is called). We only can cancel the task while the report is run. Unfortunately Export method does not raise any events that we can intercept. Also, the report running can be canceled only when the new page starts processing. Note though that the API I use lives since from ActiveReports 1.0 version(which was born in past century) but we talk about C# 5.0.

Progress reporting
Let's add the last gorgeous feature in our application. We will keep the user informed about the status of the task. The idea is very similar to the cancellation mechanism: we have the special Progress object, we report to that object in the handler of PageStart event of the report, we intercept the event of that object, we get the progress report in the handler, we update the UI with the progress report. All those actions happen in UI thread, we don't need to care about the safe UI updates. Let's look at the code(I removed the parts that do not matter for progress reporting:

private EventProgress<int> progress = new EventProgress<int>();

private string ExportReportToPdf(string rpxPath)
{
var arReport = new ActiveReport();
var reader = new XmlTextReader(rpxPath);
arReport.LoadLayout(reader);
int pageCount = 0;
arReport.PageStart += (s, a) =>
{
cancelationObject.Token.ThrowIfCancellationRequested();
(progress as IProgress<int>).Report(pageCount++);
};
// the rest of the code
}

async private void btnExportClickAsync(object sender, RoutedEventArgs e)
{
cancelationObject = new CancellationTokenSource();
try
{
this.Title = "In process...";
btnExport.IsEnabled = false;
btnCancel.IsEnabled = true;
var task = TaskEx.Run(() => ExportReportToPdf(@"C:\AnnualReport.rpx"));
progress.ProgressChanged += (s, args) =>
{
this.Title = "Pages done: " + args.Value.ToString();
};
await task;
// the rest of the code
}

VoilĂ ! We've made the UI responsive, added cancellation and progress reporting support for our long-running report task by using the new C# 5.0 features. And they look very comfortable to use so far. The code is clean and very readable(at least I believe in that :-))

Next time
Now let's imagine that we want to merge two huge reports and export the resulted document in PDF. How we can start two reports at the same time and have the UI responsive and await until both of them are completed? C# 5.0 may help too! Look for the Next episode of AsyncActiveReporting in 1 week :)

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 18, 2010

ActiveReports: Show the Data from the Windows Azure Table Storage

Preface
Some time ago I blogged about using Data Dynamics Reports to visualize the data that are stored in Windows Azure Table Storage. Originally I planned to compose that blog post around both of Data Dynamics Reports and ActiveReports but then I found that the latter has the issues with running against a cloud environment. But ActiveReports team did the great job for preparing Service Pack 1 which includes the fix for those issues! And this blog post repeats the "show the table storage data" walkthrough for ActiveReports6 SP1. I believe that it will be useful for someone.

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 ActiveReports 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 ActiveReports.
(1) Get started with Windows Azure - install the prerequisites and SDK v1.1. Install the latest release of ActiveReports6.
(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 ActiveReports.
(25) Add the new ActiveReports 6(code-based) File in the Web Role Project(in the root folder). Name it "Contacts.cs".
(26) Add two textboxes to the detail section of the report. Set DataField property to "Name"(without quotes) for the 1st textbox. Set DataField property to "Address"(without quotes) for the 2nd textbox.
(27) Add the new Web Form in the Web Role Project. Name the new form "ARReportForm.aspx".
(28) Add ActiveReports6 web-based report viewer in ARReportForm page.
(29) Add the following code to the Page_Load event handler of ARReportForm:

var rpt = new Contacts();
var ds = new ContactDataSource();
rpt.DataSource = (from c in ds.Select() select new {c.Name, c.Address}).ToArray();
rpt.Run(false);
WebViewer1.ClearCachedReport();
WebViewer1.Report = rpt;

ActiveReports allows you to use the collection of the objects as the data source, so we can use the result that is returned by ContactDataSource.Select method.
(30) Set ARReportForm.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, 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.

Sunday, February 7, 2010

A few thoughts about no-touch printing in ActiveReports6

Preface
I am not sure what is the proper term for the functionality that allows to print a document with no user interaction. In this post I am using "no-touch printing" term. For instance: Every morning Jon Smith turns on his computer at the office and runs his favorite internet browser. The browser opens the start page which is the company web-site. The start page of the company web-site indicates that Jon Smith has logged in and prints the daily schedule on the default printer that is set up on Jon's computer. Let's see how we can implement such scenario using ActiveReports6.

ActiveX Viewer And Flash Viewer.
ActiveX Viewer was introduced in the COM version of the product. ActiveReports was growing up, .NET framework became the product environment, but ActiveX Viewer never was dead in the water, and with good reason! ActiveX Viewer provides developer with the rich capabilities of displaying and printing the reports on the web. For example it's pretty easy to implement no-touch printing on the web with ActiveX Viewer. The code should load the report in the viewer, then wait until the loading is finished and then call PrintReport method. The knowledge base of ActiveReports has several articles that show the sample of such code.
Of course the main disadvantage of ActiveX Viewer is it's ActiveX component. ActiveX components officially operate only with IE and Windows. In our days the usage share of IE is only ~60%. Developers need more uniform technology that allows displaying and printing reports on the web. In ActiveReports6 ActiveX viewer is not supported but Flash Viewer is introduced instead.
Flash Viewer operates with any browser and any OS where the flash player is installed.
The bad news are Flash Viewer does not support no-touch printing.
So, it seems that there are no ways to implement no-touch printing on the web with ActiveReports6? Not exactly!

Turn around.
To display PDF documents in browser Adobe Acrobat Reader includes the Acrobat ActiveX control for Internet Explorer and the Adobe PDF Plug-In For Firefox and Netscape. ActiveX control or FF plug-in can be hosted by the HTML page and a PDF document can be loaded and printed using Javascript or Visual Basic code of that page. I am confident that no-touch printing is possible to implement with Acrobat ActiveX control and I believe that it's possible with Adobe FF plug-in as well. In other hand ActiveReports6 includes the RPX handler which can return the report is saved in PDF format...No more words! Here is sample of code which performs no-touch printing of the report using Acrobat ActiveX control:

<html>
<body onload="window.setTimeout('em.PrintAll();', 5000);">
<div id="PdfContainer">
<span style="font-size: large;">Please wait for a few seconds and the printing will be starting.</span>
<embed id="em" width="0" height="0" src="HelloWorld.rpx?OutputFormat=PDF" />
</div>
</body>
</html>

The code uses EMBED tag to host the PDF document which is returned by RPX handler. PrintAll method prints the entire document without displaying a user dialog box. There are two issues with that code. First of all, it uses the timeout approach to wait until the document is loaded in PDF viewer. I think it's possible to intercept the event which is raised by PDF control when the document loading is completed, I just don't know the exact way to do that. The second issue is the warning which appears before printing starts:

Once "Don't show this message again" checkbox is unchecked, the code provides the no-touch printing, really.

Great Silver Hope
Eventually we will add the Silverlight-based Reports Viewer for ActiveReports. At the moment the most recent version of Silverlight is v.4 Beta 2 and it's new features list includes:
Comprehensive printing support enabling hardcopy reports and documents as well as a virtual print view, independent of screen content.
Unfortunately, that "comprehensive printing support" does not include no-touch printing. The great review of Silverligh 4 printing is available here.
Perhaps the final version of Silverlight 4 or 5 will support no-touch printing. Then we will add the Silverlight-based Reports Viewer and no-touch printing will be fully supported in ActiveReports. That's what I called "Great Silver Hope":)

Sunday, January 31, 2010

The fresh glance at using ActiveReports 6 in ASP.NET MVC.

Preface
I am a big fan of ASP.NET MVC. I never liked developing of the web-applications using CGI, PHP, ASP, JSP and finally ASP.NET. Remember "The reason most people want to program for the web is that they're not smart enough to do anything else" discussion? I agree with Michael Braude. In other hands ASP.NET MVC provides developers with the solid framework with which you work with Model-View-Controller design pattern, use well-known practices to follow DRY principle, extend the native functionality to achieve the specific requirements, etc. It's just beautiful. Indeed I was excited about using ActiveReports and Data Dynamics Reports in ASP.NET MVC applications. We published the screencast which shows how to use the ActiveReports WebViewer in ASP.NET MVC application, but this way is not essential for ASP.NET MVC framework. In this article I am going to demonstrate how to create ActiveReports6 WebViewer control for ASP.NET MVC.

ASP.NET MVC and server-side controls.
The most annoying thing about ASP.NET MVC is it's not designed for use with the ASP.NET server-side controls. Custom Controls Everywhere and ASP.NET MVC totally covers that subject. Of course this article was written two years ago, but the main thought is still truth. ASP.NET MVC provides developers with the bunch of the helper methods which allow to create the UI and all the controls which you can create are client-side. The typical ASP.NET MVC approach to render the UI is shown here. Of course it is possible to use the server-side control on ASP.NET MVC applications. For example, we made sure that both of ActiveReports and Data Dynamics Reports web-viewer controls which are server-side ASP.NET controls work fine against the ASP.NET MVC framework. But the way which should be used to get them working is not essential...Imagine how it would be nice to just write the single line of code in ASP.NET MVC View page:

<%=Html.WebViewerFor("reportViewer", Model, new {width="100%", height="100%"}) %>

to get web-viewer working. Well, this is what we will achieve at the end of this article.

ActiveReports 6 Flash Viewer Secrets.
One of the cool new features of ActiveReports 6 is the new Flash-based web-viewer. Flash-based viewer loads RDF files and displays the report content. There is one problem about Flash Viewer though. The public API allows loading the report and customizing the UI using the server-side helpers only. We recently got a bit of criticism regarding the first problem and I provided the work around the problem. This turn around allows to render the content which displays flash viewer and specifies the report to show in the viewer and all those actions are performed on client-side. This Flash Viewer "secret" is the base for ActiveReports 6 ASP.NET MVC control.

XML-based reports and RPX handler.
Here I should note that all the further discussion and code suppose that the reports which are used in web-application are xml-based and RPX handler is set up. RPX handler provides the comfortable way to pass the report parameters and return the report is saved in RDF format. For example - there is xml-based Customers.rpx report which is saved in the root of my web-application. This report shows the list of the customers from the certain country. The country is specified via the report parameter. RPX HTTP handler allows to use the following URL to return the list of British customers in RDF format: http://<server url>/Customers.rpx?Country=Britain&OutputFormat=Rdf3

Creating the View Model for Flash Viewer.
The UI rendering in ASP.NET MVC deals with the Models and Views. The Model is the instance of some class which contains all the information which is needed to render the UI. For example, the list of the Orders is passed in the View and it renders the UI which shows HTML table where each row displays the Order details. Let's compose the class which encapsulates all the information which is needed for Flash Viewer in order to show the report. As I mentioned before, Flash Viewer accepts the RDF documents. To build the URL which returns the RDF document using RPX HTTP Handler we need to know the report name and the parameters values. So, the Flash Viewer View Model class is simple:

public class ReportViewModel
{
   public ReportViewModel()
   {
      ReportParameters = new Dictionary();
   }
   public string ReportName { get; set; }
   public Dictionary<string, object> ReportParameters { get; private set; }
}


Implementation of the HTML Helper for Flash Viewer Rendering.
The HTML Rendering Helper in ASP.NET MVC is the extension method for System.Web.Mvc.HtmlHelper class. This method should accept the instance of ReportViewModel class and other parameters, the code explains it better:

public static class FlashViewerExtensions
{
   public static string FlashViewerFor(this HtmlHelper htmlHelper, string name,       ReportViewModel model)
   {
      return FlashViewerFor(htmlHelper, name, model, null);
   }

   public static string FlashViewerFor(this HtmlHelper htmlHelper, string name,       ReportViewModel model, object htmlAttributes)
   {
      var tagBuilder = new TagBuilder("object");
      tagBuilder.MergeAttribute("type", "application/x-shockwave-flash");
      tagBuilder.MergeAttribute("id", name);
      // the following attribute is needed for non-IE browsers.
      tagBuilder.MergeAttribute("data", "/ActiveReports.FlashViewer.swf");
      if(htmlAttributes!=null)
      {
         IDictionary actualAttributes = new RouteValueDictionary(htmlAttributes);
         foreach(var key in actualAttributes.Keys)
         {
            tagBuilder.MergeAttribute(key, htmlHelper.Encode(actualAttributes[key]));
         }
      }
      var innerContent = new StringBuilder();
      innerContent.AppendLine(MovieParameter);
      innerContent.AppendLine(GetFlashVarsParameter(model, htmlHelper));
      tagBuilder.InnerHtml = innerContent.ToString();
      return tagBuilder.ToString(TagRenderMode.Normal);
   }

   private static string MovieParameter
   {
      get
      {
         if(_movieParameter == null)
         {
            var tagBuilder = new TagBuilder("param");
            tagBuilder.MergeAttribute("name", "movie");
            tagBuilder.MergeAttribute("value", "/ActiveReports.FlashViewer.swf");
            _movieParameter = tagBuilder.ToString(TagRenderMode.SelfClosing);
         }
      return _movieParameter;
      }
   }private static string _movieParameter;

   private static string GetFlashVarsParameter(ReportViewModel reportViewModel,       HtmlHelper htmlHelper)
   {
      var tagBuilder = new TagBuilder("param");
      tagBuilder.MergeAttribute("name", "FlashVars");
      string url = string.Format("URL={0}.rpx?OutputFormat=Rdf2",       reportViewModel.ReportName);
      foreach(string key in reportViewModel.ReportParameters.Keys)
      {
         url += string.Format("&{0}={1}", key,             reportViewModel.ReportParameters[key]);
      }
      tagBuilder.MergeAttribute("value", url);
      string ret= tagBuilder.ToString(TagRenderMode.SelfClosing);
      return ret.Replace("&amp;", "&");
   }
}

That's it! MovieParameter property returns <param name="movie" value="/ActiveReports.FlashViewer.swf" /> tag, the code supposes that ActiveReports.FlashViewer.swf seats in the root folder of the web-site. GetFlashVarsParameter method returns <param name="FlashVars" value="URL=url of the report" /> tag, it's built using the report name and the report parameters collection which are passed in the public FlashViewerFor methods. FlashViewerFor methods build <object> tag which would be displayed by any type of browser. The code uses System.Web.Mvc.TagBuilder class which is well-known practice.

The sample of usage.
Here is the code of the ASP.NET MVC Action which creates the instance of ReportViewModel class, passes that instance to the ASP.NET MVC View and returns the result:

public ActionResult Customers()
{
   var model = new ReportViewModel { ReportName = "/Reports/Customers" };
   return View("CustomersReport", model);
}

..and the code of "CustomersReport" View:

<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<%@ Import Namespace="EmptyMvcApplication.Controllers" %>
<body>
<% =Html.FlashViewerFor("reportViewer", Model, new {width="100%", height="500"}) %>
</body>

Now, the Customers action of some controller returns the page which hosts flash viewer which shows Customers report.

Finally...
ASP.NET MVC allows developer to feel like the real developer during the creation of web-based applications. The sample of the extension which is shown in this article is just a little piece of the functionality which is available with ASP.NET MVC.

Sunday, January 24, 2010

ActiveReports6 : Viewer ToolBar Secrets.

Preface
One of the cool new features of ActiveReports6 is the updated End User Designer toolbar architecture. Now the public API exposes the methods which allow to get the designer's toolbar groups and they are ToolStrips. Which opens the wide capabilities of the toolbar customization. That is fine but the question which we recently got from the clients is "What about ActiveReports6 Windows Viewer toolbar?". The way to customize the viewer toolbar seems to be unchangeable since from ActiveReports for .NET 1.0 times! ActiveReports still allows to customize the viewer toolbar using the "Toolbar API" only. Toolbar API is not the best solution ever, it is pretty limited. Fortunately a bit of digging around ActiveReporrs6 viewer internals can provide us with more comfortable way of toolbar customization.

Viewer Toolbar is actually ToolStrip!
Let's see which child controls are hosted by ActiveReports6 Viewer control. Here is the simple code which performs that:

var viewer = new DataDynamics.ActiveReports.Viewer.Viewer();
foreach(Control child in viewer.Controls)
   Debug.WriteLine(string.Format("name:{0}, type{1}", child.Name, child.GetType()));

That code emits the following output:

name:, type#op.#7s
name:tocSplitter, typeSystem.Windows.Forms.Splitter
name:TableOfContents, typeDataDynamics.ActiveReports.Viewer.TableOfContents
name:, typeSystem.Windows.Forms.ToolStrip

The first control is one which shows the report pages, the third control is one which shows the TOC, the second control is the separator between the 1st and 3rd controls and 4th control is...yes, that is viewer toolbar! In ActiveReports6 the Windows Viewer toolbar is internally implemented using ToolStrip API. Cool, let's write the simple extension method which would provide us with access to viewer's Toolbar which is the ToolStrip:


public static class ViewerHelper
{
   public static ToolStrip CreateToolStrip(this Viewer viewer)
   {
      foreach (var control in viewer.Controls)
         if (control is ToolStrip)
            return (control as ToolStrip);
      return null;
   }
}

Now we can access to viewer's toolbar using the code like:

var viewer = new DataDynamics.ActiveReports.Viewer.Viewer();
ToolStrip viewerToolStrip = viewer.CreateToolStrip();

Let's see how we can implement several customization scenarios using this "secret" feature.

Scenario #1 - Composing the complex viewer toolbar
The following code builds the top panel of the tool strip container by putting File Menu and Viewer Toolbar together:

var viewer = new DataDynamics.ActiveReports.Viewer.Viewer();
ToolStrip viewerToolStrip = viewer.CreateToolStrip();
var ms = new MenuStrip {Dock = DockStyle.Top};
var fileMenu = new ToolStripMenuItem("File");
var fileOpenMenu = new ToolStripMenuItem("Open", null, delegate{/*opening RDF file happens here*/});
var fileExitMenu = new ToolStripMenuItem("Exit", null, delegate {Application.Exit(); });
fileMenu.DropDownItems.Add(fileOpenMenu);
fileMenu.DropDownItems.Add(fileExitMenu);
ms.MdiWindowListItem = fileMenu;
ms.Items.Add(fileMenu);
toolStripContainer1.TopToolStripPanel.Join(ms,1);
toolStripContainer1.TopToolStripPanel.Join(viewerToolStrip, 2);

Scenario #2 - Customizing the native toolbar items
The following code removes the text from Print, Backward and Forward buttons and also removes Annotations button from the viewer toolbar:

var viewer = new DataDynamics.ActiveReports.Viewer.Viewer();
ToolStrip viewerToolStrip = viewer.CreateToolStrip();
viewerToolStrip.Items[2].Text = viewerToolStrip.Items[20].Text = viewerToolStrip.Items[21].Text = string.Empty;
viewerToolStrip.Items.RemoveAt(23);

Advanced scenario.
In the last code sample I used the magic numbers 2,20,21 and 23 to access to the toolbar items. How do I know those magic numbers? I got them by running the simple code which goes through the toolbar items and outputs the index, text and type of each item. Here is the resulting list:

0 Table Of Contents:System.Windows.Forms.ToolStripButton
1 :System.Windows.Forms.ToolStripSeparator
2 &Print...:System.Windows.Forms.ToolStripButton
3 :System.Windows.Forms.ToolStripSeparator
4 Copy:System.Windows.Forms.ToolStripButton
5 :System.Windows.Forms.ToolStripSeparator
6 &Find:System.Windows.Forms.ToolStripButton
7 :System.Windows.Forms.ToolStripSeparator
8 Single Page View:System.Windows.Forms.ToolStripButton
9 Multiple Page View:#is.#5ml
10 Continuous Scroll:System.Windows.Forms.ToolStripButton
11 :System.Windows.Forms.ToolStripSeparator
12 Zoom Out:System.Windows.Forms.ToolStripButton
13 Zoom In:System.Windows.Forms.ToolStripButton
14 100 %:System.Windows.Forms.ToolStripComboBox
15 :System.Windows.Forms.ToolStripSeparator
16 Previous Page:System.Windows.Forms.ToolStripButton
17 Next Page:System.Windows.Forms.ToolStripButton
18 1/1:#is.#rs+#U.#ss
19 :System.Windows.Forms.ToolStripSeparator
20 &Backward:System.Windows.Forms.ToolStripButton
21 &Forward:System.Windows.Forms.ToolStripButton
22 :System.Windows.Forms.ToolStripSeparator
23 Annotations:System.Windows.Forms.ToolStripDropDownButton

As you can see most of the toolbar items are buttons and separators. There are two mysterious items though in the list. Item 9 is one which allows to set the mode of multi-page view in the viewer. I will not focus on this element for now.
Item 18 is the text box which shows the current page number and the page total of the report which is shown by viewer. Access to the properties and events of that text box is sometimes needed. As you can see this toolbar item is the instance of some internal class of Viewer assembly. We can't access to the properties and events of that internal class, but we can try cast it to ToolStripTextBox:


var viewer = new DataDynamics.ActiveReports.Viewer.Viewer();
ToolStrip viewerToolStrip = viewer.CreateToolStrip();
var pagesEditor = viewerToolStrip.Items[18] as ToolStripTextBox;
pagesEditor.TextChanged += delegate { Debug.WriteLine("The current page was changed"); };

..and that works.

Disclaimer
The features which are described in this article are not officially supported. They are not tested and documented. They can change in further versions. Use them at your own risk.