How to Render an SSRS Report Asynchronously using the Async\Await Keywords in C# 5.0 with .NET 4.5

The .NET Framework 4.5 introduced a simplified approach to the task-based asynchronous programming model (which was introduced as part of the Task Parallel Library API in .NET 4.0) via the utilization of the two new keywords:  “async” and “await” in C# (Async and Await in VB.NET). For detailed information, see Asynchronous Programming with Async and Await (C# and Visual Basic)  on MSDN.

In this post, I’d like to show you how we could use these new keywords when making asynchronous calls to the SQL Server Reporting Services web service.  For the purpose of simplicity, I created a very simple Windows Forms project in Visual Studio 2012 in which I just have one button which invokes the SSRS web service in the click event handler.

First we need to make sure the target framework for the project is set to .NET 4.5 in Visual Studio 2012 (from the project properties).

Then we add a service reference to the SSRS web service. This part is a little tricky.  In the “Add Service Reference” dialog, after we put in the URL of the ReportExecution2005.asmx file of the Reporting Services into the Address box and locate the service, in the “Services” panel it should list the “ReportExecutionService” and if you expand that, it would show the “ReportExecutionServiceSoap” underneath. Here we select the Soap service. Then, after specifying a namespace for our reference (I just named it as “SSRSWebService”), we click on the “Advanced” button at the bottom which opens the “Service Reference Settings” dialog (as seen in the screenshots). In this latter dialog, we will make sure the “Allow generation of asynchronous operations” is checked and the “Generate task-based operations” option is selected. What this does is that Visual Studio will generate the async methods for the SOAP proxy class by using the Task<> return types.  Then we click “OK” and close the dialog.

HowtorenderSSRSreport_image1 HowtorenderSSRSreport_image2

Here I would like to underline one key part. In the second step we added the reference to the SSRS web service as a “service reference” (just like a WCF service), not as the legacy web reference (which was the old way of adding web service references prior to .NET 3.5 and WCF).  One other thing I want to point out is that here I am using the ReportExecutionServiceSoapClient proxy class, not the ReportExecutionService class which would have been the case had I added the ASMX reference as a legacy web reference. The interfaces exposed by SSRS differ slightly (different method signatures and different members) between when it is added as a WCF service (in this case it is the SoapClient) versus a legacy web reference.  Since my goal is to use the new .NET 4.5 features, I had to create the service reference in the new WCF way.

In my WinForms project I render the report located at the /MyReports/Report1 directory in SSRS as a PDF file. Here is the code for the button’s click event handler:

// Here we have to define the method as "async" (hence the "async" keyword)
private async Task RenderReportAsync(string reportPath)
{
	// Instantiate the Soap client
	ReportExecutionServiceSoapClient rsExec = new ReportExecutionServiceSoapClient();
	// Create a network credential object with the appropriate username and password used
	// to access the SSRS web service
	NetworkCredential clientCredentials = new NetworkCredential("MyUserName", "MyPassword");

	// Here, for testing purposes, I use impersonation for a local account on the SSRS server
	rsExec.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
	rsExec.ClientCredentials.Windows.ClientCredential = clientCredentials;

	string historyID = null;

	TrustedUserHeader trustedUserHeader = new TrustedUserHeader();
	ExecutionHeader execHeader = new ExecutionHeader();

	// Here we call the async LoadReport() method using the "await" keyword, which means any code below this method
	// will not execute until the result from the LoadReportAsync task is returned
	LoadReportResponse taskLoadReport = await rsExec.LoadReportAsync(trustedUserHeader, reportPath, historyID);
	// By the time the LoadReportAsync task is returned successfully, its "executionInfo" property
	// would have already been populated. Now the remaining code in this main thread will resume executing
	if (taskLoadReport.executionInfo != null)
	{
		execHeader.ExecutionID = taskLoadReport.executionInfo.ExecutionID;
	}

	string deviceInfo = null;
	string format = "PDF";

	// Now, similar to the above task, we will call the RenderAsync() method and await its result
	RenderRequest renderReq = new RenderRequest(execHeader, trustedUserHeader, format, deviceInfo);
	RenderResponse taskRender = await rsExec.RenderAsync(renderReq);
	// When the result is returned and if it is successfull, the rendered report's byte[] should be available
	if (taskRender.Result != null)
	{
		// Here for testing purposes I just writhe returned byte[] into a file on the server
		using (FileStream stream = File.OpenWrite("SSRS_Report_Async.pdf"))
		{
			stream.Write(taskRender.Result, 0, taskRender.Result.Length);
		}
	}
}

// In the event handler of the button click, again we have to mark it as "async" since
// we will be calling another async method and "await" its results
// - Note: The “await” operator can only be used within an async method
// otherwise the compiler will complain about it
private async void button1_Click(object sender, EventArgs e)
{
	button1.Enabled = false;

	await RenderReportAsync(textBox2.Text.Trim());

	textBox1.Visible = true;
	textBox1.Text = string.Format("Report Saved (ASYNC)");
}

To contrast this to the synchronous way of calling the SSRS web service, here is the synchronous version of the RenderReport method:

private void RenderReport(string reportPath)
{
	ReportExecutionServiceSoapClient rsExec = new ReportExecutionServiceSoapClient();
	NetworkCredential clientCredentials = new NetworkCredential("MyUserName", "MyPassword");

	rsExec.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
	rsExec.ClientCredentials.Windows.ClientCredential = clientCredentials;

	string historyID = null;

	ExecutionInfo execInfo = new ExecutionInfo();
	TrustedUserHeader trusteduserHeader = new TrustedUserHeader();
	ExecutionHeader execHeader = new ExecutionHeader();
	ServerInfoHeader serviceInfo = new ServerInfoHeader();

	rsExec.LoadReport(trusteduserHeader, reportPath, historyID, out serviceInfo, out execInfo);
	execHeader.ExecutionID = execInfo.ExecutionID;

	string deviceInfo = null;
	string extension = null;
	string encoding = null;
	string mimeType = null;
	Warning[] warnings = null;
	string[] streamIDs = null;
	string format = "PDF";
	Byte[] result;

	rsExec.Render(execHeader, null, format, deviceInfo, out result, out extension, out mimeType, out encoding, out warnings, out streamIDs);

	using (FileStream stream = File.OpenWrite("SSRS_Report_Sync.pdf"))
	{
		stream.Write(result, 0, result.Length);
	}
}

private void button2_Click(object sender, EventArgs e)
{
	button2.Enabled = false;
	RenderReport(textBox2.Text.Trim());

	textBox3.Visible = true;
	textBox3.Text = string.Format("Report Saved (SYNC)");
}

When we run this application, one important difference that we notice is that the synchronous version blocks the whole UI for the user until the report rendering is complete. On the other hand, when using the asynchronous version, the UI remains responsive.

Hope this was helpful!

Related posts: