Transitioning between SOAP and URL Access
The SSRS Web Server supports two basic protocols, SOAP and URL Access. The SOAP API is great for managing the catalog and doing some session management within the context of a report session, but is generally not the greatest thing in the world when actually rendering a report. The problem is that since the SOAP API returns a byte[] from the Render() and RenderStream() methods, your client application has to hold the entire contents in memory. In addition, because of how ASP.Net's SOAP serialization works on the server, there is some additional memory overhead when rendering a report through the SOAP API. For this reason, I recommend that you consider using our URL Access API to render reports via a simple HTTP GET request. The problem with this though is that if your report contains many parameters or data source credentials or if you need some other kind of interactivity then using pure URL Access can be a bit cumbersome. A nice solution for this problem is to mix usage of both the SOAP API and URL Access. The key is simply communicating the proper execution id for all of the requests.
For some background on how to use URL Access and the SOAP API please check out these links:
The easiest way to show this is by way of an example. In my scenario I want to render a report to PDF, but I want to specify the data source credentials via the SOAP API.
The first step is to create the session and bind it to a report. This is done via the ReportExecutionService.LoadReport method. We then query the returned ExecutionInfo object to determine which data sources need credentials and provide the credentials. Once that is complete, we are good to go and we can construct the URL to render to the report from, including the output format and the session id which was returned as part of the ExecutionInfo. It is also possible to retrieve the ExecutionId property from the SOAP header on the ReportExecutionService object.
In this example, the report that I rendered turned out to be ~ 1MB in length. Rather than having to allocate that entire byte[] in memory, I am instead able to stream the results from the WebResponse object in 4KB buffers.
The complete code for this example is here:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net;
using Example.ReportExecution;
class Program
{
static void Main(string[] args)
{
try
{
using (ReportExecutionService re = new ReportExecutionService())
{
re.UseDefaultCredentials = true;
Console.WriteLine("Loading the report...");
ExecutionInfo execInfo = re.LoadReport("/ProductCatalog", null);
if (execInfo.CredentialsRequired)
{
List<DataSourceCredentials> credentials = new List<DataSourceCredentials>();
foreach (DataSourcePrompt dsp in execInfo.DataSourcePrompts)
{
DataSourceCredentials cred = new DataSourceCredentials();
cred.DataSourceName = dsp.Name;
cred.UserName = "asdf";
cred.Password = "mypassword";
credentials.Add(cred);
}
Console.WriteLine("Setting data source credentials...");
execInfo = re.SetExecutionCredentials(credentials.ToArray());
}
string format = "PDF";
string requestUri = string.Format(
@"https://localhost/reportserver/?{0}&rs:SessionId={1}&rs:Format={2}",
execInfo.ReportPath,
execInfo.ExecutionID,
format
);
WebRequest request = WebRequest.Create(requestUri);
request.UseDefaultCredentials = true;
Console.WriteLine("Request report...");
using (WebResponse response = request.GetResponse())
using (Stream readStream = response.GetResponseStream())
using (FileStream writeStream = new FileStream(@"c:\output.pdf", FileMode.Create))
{
byte[] readBuffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = readStream.Read(readBuffer, 0, readBuffer.Length)) != 0)
{
writeStream.Write(readBuffer, 0, bytesRead);
}
}
Console.WriteLine("Request finished...");
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}