It turns out that, in an SSRS server report, it is not
directly possible to enumerate the parameters and their values. This is commonly needed when displaying parameter summary tables to the user so they can know which parameters they used to generate the report.
You will notice that I said it’s not
directly possible. I say this because, well, basically anything and everything is possible when it comes to software (you can always build a new layer of indirection…”). It really boils down to a question of how much time it will take to develop, how ugly it’s going to be, and what sort of maintenance headaches it will cause. In the end, if it saves a lot of time, then you can usually deal with a little ugliness.
Here’s a solution to the aforementioned problem.
Public Shared Function ParameterDisplay(ByVal reportParameters As Parameters) As String
Dim m_nameMapField As System.Reflection.FieldInfo = reportParameters.GetType().GetField("m_nameMap", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
Dim m_nameMap As System.Collections.Hashtable = TryCast(m_nameMapField.GetValue(reportParameters), System.Collections.Hashtable)
Dim sb As New System.Text.StringBuilder()
For Each parameterName As String In m_nameMap.Keys
Dim parameter As Parameter = TryCast(reportParameters(parameterName), Parameter)
sb.Append(parameterName)
sb.Append(": ")
If parameter.IsMultiValue Then
Dim values() As Object = TryCast(parameter.Value, Object())
For i As Integer = 0 To values.Length - 1
If values(i) Is Nothing Then
sb.Append("NULL")
Else
sb.Append(values(i))
End If
If i <> values.Length - 1 Then
sb.Append(", ")
End If
Next i
sb.AppendLine()
Else
If parameter.Value Is Nothing Then
sb.AppendLine("NULL")
Else
sb.AppendLine(parameter.Value)
End If
End If
Next parameterNameReturn sb.ToString()
End Function
You can then use an RDL expression to reference this function. This expression would simply be:
=Code.ParameterDisplay(Parameters)
You will notice that we are reflecting on the Parameters Type. For a server report, this is an implementation of the Microsoft.ReportingServices.ReportProcessing.ReportObjectModel.Parameters abstract class found in the Microsoft.ReportingServices.ProcessingObjectModel.dll assembly. This assembly should be located in the following directory:
C:\Program Files\Microsoft SQL Server\MSSQL.4\Reporting Services\ReportServer\bin\
In that same directory, there is an assembly named Microsoft.ReportingServices.ProcessingCore.dll. This assembly holds the concrete implementations for the Types in the previously mentioned assembly. The type that we care about is Microsoft.ReportingServices.ReportProcessing.ReportObjectModel.ParametersImpl. If we crack this type open using Reflector we can see that it, unfortunately, is not enumerable. However, it does maintain an internal Hashtable instance that maps parameter names to their corresponding indexes. In the above code snippet I can reach in to grab this Hashtable instance and then enumerate it. It’s ugly, yes, but it works and you won’t have anything to maintain.
This approach, of course, comes with several caveats:
1) It requires Full Trust
2) The code will break if future implementations (e.g. SQL Server 2008, which I have not had time to look into) of the Parameters type change. I hope they do.
This problem brings up an perplexing design question. Why does the processing object model not have a 1:1 relationship with the RDL document structure? This, in my opinion, is a grotesque oversight. If, for example, you have a 1:many collection of elements then the processing object model should have a corresponding enumerable collection. Every property and attribute in RDL should have a matching property in the processing object model. You should be able to interact with everything in the document structure programmatically at runtime without using ugly hacks.