Sunday, October 26, 2014

Cannot access Properties.Settings from App.xaml.vb

I don't generally like the Settings class in the .Net framework because it does not work well with ClickOnce deployment. If you alter the settings collection between deployments, the new deployment thinks the users' settings files are corrupt and refuses to run until you destroy them. This pretty much renders settings useless.

However I noticed another problem the other day while messing around. Let's pretend your project has a setting of "Name" defined as a string with user scope. To access this setting in the main window you would use...

String Name;
Name = Properties.Settings.Default.Name;

Now try the same thing in App.xaml.vb and the compiler will complain that Settings is not valid.


The clue is buried in the error message. It's expecting a dictionary. What's happening here is that the compiler is confusing Properties.Settings with Application.Current.Properties(...) and it's expecting a dictionary key (or some dictionary reference) to follow "Properties".

To reference the settings from the App.xaml.vb you need to explicitly prefix "Properties" with the default namespace ie.

String Name;
Name = Lesson_2.Properties.Settings.Default.Name;

The best approach would be to always use the fully qualified reference for both Properties.Settings and Application.Current.Properties ie.

String Name;
Name = Lesson_2.Properties.Settings.Default.Name;
Application.Current.Properties["Name"] = Name;

Of course, Visual Basic uses the My syntax to access settings which avoids this confusion completely.


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