Tuesday, October 21, 2014

Reporting Services generates invalid pdf files

Let's be honest here - the fault was all mine although it took a long time to realize why.

This isn't really a WPF issue at all, but it is a WCF issue and I'm only using WCF because the client is in WPF. The question is "How do I tell Reporting Services to render a report and return the pdf to me so I can send it back to the client?" I have a WCF service doing this so I can take more control over parameters, security, etc.

There's dozens of ways to do this and I explored most of them. The one that made most sense to me was to create a web request object and grab the response in a web response. Then I save the ResponseStream to a temporary file which gives me the option to dynamically stitch a bunch of reports together using ExpertPDF and return a single pdf file to the client as a byte array.

You have to be very careful to use the correct methods and understand how they work. For example, System.IO.File.ReadToEnd returns a string which can cause pdf files (which can be binary) to be corrupted. Also Stream.Read does not necessarily read all the bytes you asked for. You have to keep calling it until the stream is fully read.

If you only execute a single call to Stream.Read you may not completely populate the byte array - and your pdf file will be corrupted.

Here's the code to execute a report and save it to a temporary file...

    Public Function RunReportingServicesReport() As String

        Dim URL As String
        Dim Request As System.Net.HttpWebRequest = Nothing
        Dim Response As System.Net.HttpWebResponse = Nothing
        Dim ReportName As String = Nothing
        Dim Stream As System.IO.Stream = Nothing
        Dim Bytes As Byte()
        Dim BytesRead As Integer = 0
        Dim BytesToRead As Integer
        Dim Offset As Integer = 0
        Try
            URL = "http://ReportServer/ReportService?/Reports/MyReport&rs:Format=PDF&rs:Command=Render"
            Request = System.Net.WebRequest.Create(URL)
            Request.PreAuthenticate = True
            Request.Credentials = New System.Net.NetworkCredential("User", "Password", "Domain")

            Response = Request.GetResponse()
            ReportName = System.IO.Path.GetTempFileName()
            Stream = Response.GetResponseStream()
            BytesToRead = Response.ContentLength
            Bytes = New Byte(BytesToRead - 1) {}
            Do While BytesToRead > 0
                BytesRead = Stream.Read(Bytes, Offset, BytesToRead)
                Offset += BytesRead
                BytesToRead -= BytesRead
            Loop
            System.IO.File.WriteAllBytes(ReportName, Bytes)

        Catch ex As System.Exception
            Throw New System.Exception("RunReportingServicesReport:" & ex.Message)
        Finally
            If Stream IsNot Nothing Then Stream.Close()
            If Response IsNot Nothing Then Response.Close()
        End Try

        Return ReportName

    End Function

No comments:

Post a Comment