How to render reports asynchronously by using the async and await keywords
Environment
| Product | Progress® Telerik® Reporting |
| Framework | .NET Framework 4.5 and higher |
Description
.NET Framework 4.5 introduced the Task<T> class and the async and await keywords, which simplify parallel programming. This article explains how to use these features with the Telerik Reporting ReportProcessor class to render reports asynchronously, and how to pass report parameters to reports that require them.
Solution
The ReportProcessor class and its RenderReport method provide all the functionality needed for programmatic report generation. Because RenderReport returns a RenderingResult, wrap the call in a method that returns Task<RenderingResult> to use it asynchronously:
public class AsyncWrappers
{
// Wrap the RenderingResult like this:
public async Task<RenderingResult> RenderReportAsync(Type reportType) // Pass parameters here, like device info and report to render
{
ReportProcessor reportProcessor = new ReportProcessor();
// Apply any deviceInfo settings if necessary
Hashtable deviceInfo = new Hashtable();
// Any other Report Source can be used instead
// For example InstanceReportSource can be used if the instantiated report is passed as parameter of the method
TypeReportSource typeReportSource = new TypeReportSource();
typeReportSource.TypeName = reportType.AssemblyQualifiedName;
return await Task.Run(() => reportProcessor.RenderReport("PDF", typeReportSource, deviceInfo));
}
}The method returns Task<RenderingResult> and is marked async. The RenderReport call is wrapped in Task.Run and awaited, offloading the blocking render work to a thread-pool thread. Use the wrapper in your application code:
//Use the wrapper in your code like this:
public async Task<string> RenderReportAsync()
{
var asyncWrappers = new AsyncWrappers();
Console.WriteLine("Rendering started on: {0}", Thread.CurrentThread.ManagedThreadId);
var result = await asyncWrappers.RenderReportAsync( typeof(Telerik.Reporting.Report) );// pass the type of your report here
string fileName = result.DocumentName + "." + result.Extension;
string path = System.IO.Path.GetTempPath();
string filePath = System.IO.Path.Combine(path, fileName);
using (System.IO.FileStream fs = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
{
fs.Write(result.DocumentBytes, 0, result.DocumentBytes.Length);
}
Console.WriteLine("Rendering finished on: {0}\n", Thread.CurrentThread.ManagedThreadId);
// You can return void but that is not recommended
return string.Format("Successfully rendered! File saved in {0}\n", filePath);
}Full Console Application Example
The following example invokes the wrapper from a console application. While the report renders on a background thread, the main thread continues executing Count():
class Program
{
// A working example
static void Main(string[] args)
{
// Use your class to render the report in an async manner
var myClass = new MyClass();
Console.WriteLine("Starting rendering...");
var task = myClass.RenderReportAsync();
// While the report is rendering do something else
Count();
Console.WriteLine("Main finished on: {0}", Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Render Status: {0}\n", task.Result);
Console.ReadLine();
}
static void Count()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
}
}
}Passing Report Parameters
Some reports define parameters that must be supplied before rendering. The TypeReportSource class exposes a Parameters collection where you add Parameter objects.
Extend the wrapper method to accept a parameter dictionary, then populate the collection before the Task.Run call:
public class AsyncWrappers
{
public async Task<RenderingResult> RenderReportAsync(
Type reportType,
IDictionary<string, object> parameters = null)
{
ReportProcessor reportProcessor = new ReportProcessor();
Hashtable deviceInfo = new Hashtable();
TypeReportSource typeReportSource = new TypeReportSource();
typeReportSource.TypeName = reportType.AssemblyQualifiedName;
if (parameters != null)
{
foreach (var param in parameters)
{
typeReportSource.Parameters.Add(new Parameter(param.Key, param.Value));
}
}
return await Task.Run(() => reportProcessor.RenderReport("PDF", typeReportSource, deviceInfo));
}
}Call the extended wrapper with a parameter dictionary:
var result = await asyncWrappers.RenderReportAsync(
typeof(MyReport),
new Dictionary<string, object>
{
{ "StartDate", new DateTime(2024, 1, 1) },
{ "EndDate", new DateTime(2024, 12, 31) }
});For reports that need direct access to the report instance — for example, to set properties in addition to parameters — use InstanceReportSource instead:
var report = new MyReport(); // your report class
report.ReportParameters["StartDate"].Value = new DateTime(2024, 1, 1);
report.ReportParameters["EndDate"].Value = new DateTime(2024, 12, 31);
InstanceReportSource instanceReportSource = new InstanceReportSource();
instanceReportSource.ReportDocument = report;
return await Task.Run(() => reportProcessor.RenderReport("PDF", instanceReportSource, deviceInfo));If a report has no parameters, the simplified wrapper from the first example works without modification.
Notes
Download a sample console application.