Job problem: I need regularly to generate a series of PDFs of invoices and then do some processing of these PDFs.
Prospective solution: use SQL Server Reporting Services 2008 to generate the PDFs. Then, use a console application to tell SSRS which invoices to create and do the subsequent processing. This console application would run via the Task Scheduler. All of these are technologies which our IT staff know, so maintenance by somebody other than me wouldn’t be a big hassle.
There are a few ways to run Reporting Services to generate reports without needing to connect directly to the report server through your web browser.
The ReportViewer Control
The easiest method is definitely the ReportViewer control. It works well with WinForms and WebForms applications, but unfortunately, I wanted to use a scheduled console application and have no human intervention. That left this one out in the cold, although we do use it in web applications.
RS.exe
The second method is to run RS.exe. This is a utility which allows you to run VB files (with an RSS extension) against a Reporting Services installation. This can then be called from PowerShell or a console application. Roman Rehak has a great post on this and you should read that. The command is:
rs.exe -i Test.rss -s http://[server]/reportserver -e Exec2005
Note that the “-e Exec2005” part is necessary for a Reporting Services 2008 installation. Your RSS file would be pretty simple. The one I used for testing was:
Public Sub Main() Dim format as string = "PDF" Dim fileName as String = "C:\RS\Output.pdf" Dim reportPath as String = "/[Report Folder]/Invoice" ' Prepare Render arguments Dim historyID as string = Nothing Dim deviceInfo as string = Nothing Dim extension as string = Nothing Dim encoding as string Dim mimeType as string = "application/pdf" Dim warnings() AS Warning = Nothing Dim streamIDs() as string = Nothing Dim results() as Byte rs.Credentials = System.Net.CredentialCache.DefaultCredentials rs.LoadReport(reportPath, historyID) Dim parameters(1) As ParameterValue parameters(0) = New ParameterValue() parameters(0).Name = "InvoiceNumber" parameters(0).Value = "15" rs.SetExecutionParameters(parameters, "en-us") results = rs.Render(format, deviceInfo, extension, mimeType, encoding, warnings, streamIDs) ' Open a file stream and write out the report Dim stream As FileStream = File.OpenWrite(fileName) stream.Write(results, 0, results.Length) stream.Close() End Sub
That file works. What I’m doing is passing in a single parameter—InvoiceNumber—and a value associated with that parameter. My report was already set up to generate a report based off of that one parameter. The only other place that would need to be changed is the report location, where you would need to go to the correct folder and give the right name. The name of my report was “Invoice.”
The downside to using RS.exe is that you need to script out files for each one, and then call this executable for each script. It works, and I could have a console application call it so many times, but honestly, I was looking for something a bit more elegant.
Using The Web Service
The method I ended up going with was writing code to connect to the Reporting Services web service directly. To do this in Visual Studio 2010, the first step is to add a Web Reference: Right-Click References –> Add Service Reference –> Click Advanced Button –> Click Add Web Reference Button.
Once you get to the web reference page, you’ll be prompted for a WSDL address. Type in http://%5BYour Report Server]/reportserver/reportexecution2005.asmx?wsdl. Note that you want to go to reportexecution2005 and NOT reportservice2005 for this. The reportservice2005.asmx service is for administrative functionality and the reportexecution2005.asmx service is for running reports. This changed a bit from SSRS 2005, where both sets of services used the same address, so some stuff floating around on the internet doesn’t really apply to SSRS 2008. Anyhow, after selecting the report execution service, you should give it a name. I called mine ReportingServicesReference. After you add the reference, Visual Studio will update your app.config to add some new code to connect to the server.
What you want to do next is figure out how to render your report. MSDN has some code and Will Beattie has some code. In addition, you can read the RSS file above and get a general idea of how Reporting Services methods operate. There are some minor changes between the RS.exe and web service approaches—the biggest one is that the Render method does not include a parameter list in the web service option, whereas it does in RS.exe.
Here is sample code which you can use to render the same reports:
using [your namespace].ReportingServicesReference; private static void CreateInvoicePDF(int InvoiceId, string fileName) { string FullFilePath = ConfigurationData.PDFLocation + fileName; ReportExecutionService rs = new ReportExecutionService(); string format = "PDF"; string reportPath = "/[Your Report Folder]/Invoice"; string mimeType = "application/pdf"; ParameterValue[] parameters = new ParameterValue[1]; parameters[0] = new ParameterValue(); parameters[0].Name = "InvoiceNumber"; parameters[0].Value = InvoiceId.ToString(); byte[] results = RenderReport(rs, format, mimeType, reportPath, parameters); WriteFile(results, FullFilePath); } private static byte[] RenderReport(ReportExecutionService rs, string format, string mimeType, string reportPath, ParameterValue[] parameters) { rs.Credentials = System.Net.CredentialCache.DefaultCredentials; //Prepare Render arguments string historyID = null; string deviceInfo = String.Empty; string extension = String.Empty; string encoding = String.Empty; Warning[] warnings = null; string[] streamIDs = null; byte[] results = null; rs.LoadReport(reportPath, historyID); rs.SetExecutionParameters(parameters, "en-us"); results = rs.Render(format, deviceInfo, out extension, out mimeType, out encoding, out warnings, out streamIDs); return results; } private static void WriteFile(byte[] input, string FileName) { //Open a file stream and write out the report FileStream stream = File.OpenWrite(FileName); stream.Write(input, 0, input.Length); stream.Close(); }
ConfigurationData up above is a helper class that I created to give me static typing for appConfig settings. Otherwise, this should be pretty straightforward code, whose end result is a PDF created in the directory and location of your choosing.
In this example – and confirmed from my findings – this makes three separate web service calls to the RS server. In a high volume scenario, say month-end reporting – I would like to reduce the number of calls down to one – is this possible???
Unfortunately, I don’t know how to do that or even if it is possible. Reporting Services, in my experience, is a very inefficient tool. When deploying or running reports, it makes a lot of calls, and I think that’s by design.
Kevin, thanks for show us these possibilities. I implemented the option using webservices and its works well.
Amazing . You made my day.
it keep promp me soapException was unhandled error from this line
–rs.LoadReport(reportPath, historyID)
what to do with the historyID
i need you help..
Thank you for this posting. It has provided me with a perfect solution where other possible ideas on the net seem overly complex.
Hi Kevin,
I am working on a similar requirement of executing SSRS report using Autosys Scheduler. Can you please let me know how did u configured the jil file for your project. I am looking for how to chk whether the report is executed sucessfully and desired output file is created so that the autosys job can be flaged sucess or failure.I am a little bit confused and stuck up here on how to chk the status of report execution?
I was able to execute and export report from C# program ,only part remaining is the integration with Scheduler Application .
Any help on this is much appreciated.
Thanks in advance,
Hemesh