beaucrawford.net

Give me data or give me death

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Don't show

Authors

Tags

Don't show

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010

    Reporting Services (SSRS) - Enumerate Parameters in RDL Custom Code

    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.


    Categories: C# | SSRS
    Posted by Beau on Wednesday, September 17, 2008 4:27 PM
    Permalink | Comments (25) | Post RSSRSS comment feed

    Comments

    Ghislain Leroux ca

    Friday, September 19, 2008 8:52 AM

    I dont quite understand how the above code works but I've modified it a little and I was able to obtain the fields used in a report... this is important for us cause we will be using reports where the name of the fields (labels) in the result set can and will change... A bit more work will be needed to get the number of fields and to access the field names individually but this isn't too hard to do. It would be easy to modify the code so a single field is returned.

    Public Shared Function FieldDisplay(ByVal reportFields As Fields) As String

    Dim m_nameMapField As System.Reflection.FieldInfo = reportFields.GetType().GetField("m_nameMap", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)
    Dim m_nameMap As System.Collections.Hashtable = TryCast(m_nameMapField.GetValue(reportFields), System.Collections.Hashtable)

    Dim sb As New System.Text.StringBuilder()

    For Each fieldName As String In m_nameMap.Keys

    Dim field As Field = TryCast(reportFields(fieldName), Field)

    sb.AppendLine(fieldName)

    Next fieldName

    Return sb.ToString()

    End Function

    Beau us

    Friday, September 19, 2008 7:08 PM


    I'm not sure why you would need to do what you described. Can you explain?

    You sort of got lucky. The Fields class happens to have an internal Hashtable field by the same name.

    I looked in Reflector and the class has an internal "GetFields" method that you could invoke like this:

    Dim method As System.Reflection.MethodInfo = fields.GetType().GetMethod("GetFields", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)

    Dim fieldArray() As Field = TryCast(method.Invoke(fields, Nothing), Field())

    Torgny se

    Tuesday, September 23, 2008 2:46 AM

    I've made a few tests on Sql Server 2008 developer edition and as far as I can understand it, this is NOT solved in Sql Server 2008. In other words I think your code snippet will be used a while all over the world Wink
    Ugly? No I don't think so with current situation. Alright, the proper way to do this is probably to change the process object model to match the RDL structure as you already told us. But this is not our work, Microsoft should do this. Meanwhile I think your approach is nice enough and "fit for use".

    Other work-arounds:

    Another approach is to ask the webservice for the parameters. Causes execution overhead. Not an option.

    One approach is to read ReportServers database and access/read parameters directly as you purposed in the Part II article. This is ugly in my opinion. If I had to do this, I think the best way will be to have "neutral zone" for the report metadata. In this, untested scenario, a SSIS-package will extract /Parameters/Parameter from Catalog.Parameter to a separat table (for all published reports). The report could then use a dedicated dataset for parameters (and perhaps other stuff as well).

    A lot of work just to have a nice first-page with the given parameters :-(

    But at the end of the day - we will have happy users!



    Beau us

    Tuesday, September 23, 2008 9:35 AM

    Torgny --

    I'm not sure if you noticed it, but I posted another solution here:

    beaucrawford.net/.../...-Parameters2c-part-II.aspx

    It uses a similar approach to what you mentioned with making a Web service call. It is just hitting the database directly (probably less overhead). You should get back the same metadata.

    Torgny se

    Tuesday, September 23, 2008 1:07 PM

    I did read your proposal in part II of the article.
    The "thing" about direct DB access (to ReportServer) that I don't like is the "short curcuit" kind of architecture, were we actually bypass the webservice which expose the Reporting Services functionality and use the DB access directly.

    But in this case it seems like we only can choose among bad things. Let us hope Microsoft will fix this later on, so we don't have to work so hard for these common needs.

    Meanwhile I will stick to your first solution.
    In both your solutions it's easy to change later without having to edit the reports.

    Ghislain Leroux ca

    Thursday, September 25, 2008 11:37 AM

    I did the thing with the fields (accessing the fields like the parameters) because we dont know the name of the fields that we gonna get... (Existing code is pretty messed up... like, the same stored proc can return more than one model of result set...) But, at the moment, we just want to display all the fields, from the first to the last one, from the left to the right and output the result as an Excel file. This little hack allows me to access the fields without knowing the name of each field.

    Ghislain Leroux ca

    Thursday, September 25, 2008 11:38 AM

    By the way, it's working... it was not a question but a suggestion. I should have made it clearer, sorry! Smile

    Mike DePouw us

    Monday, November 24, 2008 7:13 PM

    RE: "You should be able to interact with everything in the document structure programmatically at runtime without using ugly hacks. "

    I agree, we should create a connect feedback item. Thanks for researching and sharing what you found.

    David us

    Monday, December 08, 2008 10:49 AM

    Missing carriage return on last 2 lines of the code snippet...

    Ryan Duclos us

    Thursday, December 18, 2008 1:05 PM

    Their is another way to get the parameter info without writing custom code. You can query the report server database to get a list of parameters and display them in a table/matrix. Their will be an article on my blog soon (http://rduclos.wordpress.com/) with an example.





    Ryan Duclos us

    Thursday, December 18, 2008 2:12 PM

    Here is a link to another solution for a dynamic parameter list within the report: rduclos.wordpress.com/.../

    Vikash Mishra in

    Tuesday, February 24, 2009 5:26 AM

    I am Vikash Junior programmer. I work in report server I want to write custome code on that reporting services. My code is----
    Public Shared function MsgChk(ByVal state as String, ByVal district as String, ByVal block as String, ByVal panchayat as String) As String

    if(state ="0" and district ="0" and block ="0" and panchayat ="0" ) then
    msgbox("Sorry, No matching records found.")
    end if
    End function

    I add this code look like this but the report will be display in local as soon as we deploy this. the server display #Error if no record found case
    I call this function look like this
    =code.MsgChk(Sum(Fields!stcount.Value, "DS_SearchCount"),Sum(Fields!dtcount.Value, "DS_SearchCount"),Sum(Fields!blockcount.Value, "DS_SearchCount"),Sum(Fields!gpcount.Value, "DS_SearchCount"))

    Gopal in

    Wednesday, March 04, 2009 5:32 AM

    hi all,

    am new for reporting services i want to select the fields in favour of the user input in the run time from rdl files which is in reportserver. for eg the user can given the employee number it will displays the full details of the employee. how can i retrive dynamicaly from rdl reports.

    Adam ca

    Friday, March 13, 2009 11:45 AM

    Great example, implemented and worked first time in Visual Studio 2005 report viewer, once deployed to our DEV SSRS server it throws and error on the m_nameMapField.GetValue(reportParameters), Line 2

    Microsoft.ReportingServices.ReportProcessing.ReportObjectModel.ParametersImpl.m_nameMap
    at System.Reflection.RtFieldInfo.PerformVisibilityCheckOnField(IntPtr field, Object target, IntPtr declaringType, FieldAttributes attr, UInt32 invocationFlags)
    at System.Reflection.RtFieldInfo.InternalGetValue(Object obj, Boolean doVisibilityCheck, Boolean doCheckConsistency)
    at System.Reflection.RtFieldInfo.GetValue(Object obj)
    at ReportExprHostImpl.CustomCodeProxy.ParameterNameDisplay(Parameters reportParameters)

    We have FULL Trust and very limited security restrictions on the server, its pretty much wide open. I have tried breaking down each object to see where the problem is but the code editor is so limited and frustrating as I am sure you are well aware. I am open to just about anything, I have started writing the function in a separate dll but I have a feel I am going to meet with the same problems or worse.

    Here is my code
    Public Shared Function ParameterNameDisplay(ByVal reportParameters As Parameters) As String

    Dim table as System.Collections.Hashtable
    Dim sb As New System.Text.StringBuilder()
    m_nameMapField = reportParameters.GetType().GetField("m_nameMap", System.Reflection.BindingFlags.Instance Or System.Reflection.BindingFlags.NonPublic)

    Try
    Dim m_nameMap As System.Collections.Hashtable = TryCast(m_nameMapField.GetValue(reportParameters), System.Collections.Hashtable)
    Catch ex As Exception
    sb.Append("Error: " & ex.Message & vbcrlf & ex.StackTrace.ToString())
    End Try
    Return sb.ToString()
    End Function

    Vernon Jimmerson us

    Monday, March 16, 2009 11:52 AM

    I have the same issue as Adam. He is correct in that the declaration

    Dim m_nameMap As System.Collections.Hashtable = TryCast(m_nameMapField.GetValue(reportParameters), System.Collections.Hashtable)

    seems to be the problem. My envoronment is setup with what I think is called a public facing server. A seperate SSRS server from the one that has the DBEngine.

    This is the nicest solotion I have seen so far if if would only work when deployed.

    Beau us

    Monday, March 16, 2009 6:06 PM

    Vernon - Have you tried pulling the assembly off the server and looking at it in Reflector? See what the data type is for the "m_nameMap" variable and then modify the TryCast call accordingly.

    This, as you have discovered, is the problem with doing Full Trust reflection that relies on private implementation details -- those details can change and you will never know.

    Ryan Duclos us

    Tuesday, March 17, 2009 8:03 AM

    Why are you trying to Enumerate through the parameter collection?

    If the answer is to display the parameters in the report, the easier solution is to query the Reporting Services database. A couple of ways you can go about it.
    Examples:
    rduclos.wordpress.com/.../reporting-services-2005-dynamic-parameter-list
    beaucrawford.net/.../...-Parameters2c-part-II.aspx

    If you are trying to Enumerate through the parameters for another reason let us know, and we will try to come up with a solution.

    Vern us

    Wednesday, March 18, 2009 9:49 AM

    I haven't tried looking at your code example through reflector however I did find another method that works for me(below).
    The example was on a microsoft site. I have modified it a little. It doesn't enumerate the parameters but will list specific parameters you setup.
    It handles mutiple parms or single parms. I have a lot of parms that I don't want to show so enumerating them isn't the best method for me anyway.

    e.g.

    =code.ShowParameterValues("Date Grouping", Parameters!DatePartId, 0)
    + code.ShowParameterValues("Data Set", Parameters!DataSetId, 1)
    + code.ShowParameterValues("Date Range", Parameters!DateRangeId, 1)



    Public Shared Function ShowParameterValues(ByVal strParmLabel as String, ByVal parameter as Parameter, PrefixCRLF as integer) as String
    '--------------------------------------------------------------------------------------------------
    '-- Use Fixed width font for alignment. Courier New, 8pt works good.
    '--------------------------------------------------------------------------------------------------
    On Error Goto ProcError

    Dim s as String
    Dim spadchar as String
    Dim intLabelChars as integer
    spadchar = " "
    intLabelChars = 20

    If parameter.IsMultiValue then
    s = ""
    For i as integer = 0 to parameter.Count-1
    s = s + CStr(parameter.label(i)) + " "
    Next
    Else
    s = CStr(parameter.Value)
    End If

    If Not String.IsNullOrEmpty(s)
    s = strParmLabel + spadchar.PadRight(intLabelChars - strParmLabel.length, spadchar) + ":" + s
    If PrefixCRLF = 1
    s = CHR(13) + CHR(10) + s
    End if
    End If

    Return s

    ProcError:
    Return err.description

    End Function

    slihp gb

    Friday, March 20, 2009 11:56 AM

    Ray we are trying to Enumerate through the parameter collection as we want to get both the parameter name and value passed to it, for all parameters on report, this wiould allow us to use a single line of code to pupulate a text box with all the parameters and there values. Both Beau solutions are the best but like the others i get errors when i uploade to the server. the first on m_nameMapField.GetValue(reportParameters), and on the second example i gett a trusted security error when trying to set the xml doc - Dim doc As New System.Xml.XmlDocument().


    any solutions as this would be perferct if it worked when uploaded to the server.

    p

    Beau us

    Friday, March 20, 2009 1:14 PM

    slihp -- Please send me the "Microsoft.ReportingServices.ProcessingObjectModel.dll" assembly from your production server and I will take a look at it in reflector. I tested the solutions I posted here with the Developer version of SQL Server 2005 so there may be some differences with service packs, etc.

    Vern us

    Saturday, March 21, 2009 3:22 PM

    << trying to Enumerate through the parameter collection >>

    This sounded like what I wanted to do until I though about it a bit more. I quite oftern try t do something only to realize to isn't really what I really need. As far as this subject goes I would want to be able to enumerate the parameters and parameter labels in an assembly DLL. Doing it in vb.net code inside of each reports has not worked for me yet (when deployed on an internet facing server) and seems to me like the wrong way to expend my efforts as I have found that coding inside of reports should be minimized as much as possible.
    Code in SSRS reports really needs to be in an assembly/DLL instead s code in a 100+ reports becomes unmanageable.
    In my case I set up all my reports to use the same parameters which include a number of hidden parameters / drill down parameters.
    I have post fixed the parameters for the routines providing the values for the drill values with _prm_(procedure name) so I can identify them in the reports. If I did have a way to enumerate the parametrs I would need to use the post fix and/or the hidden property of the parameters to decide whether or not to display it. A parameter may or may not be actually used in a particular report. So in my case with the following parameter list I would know that I don't want anything post fixed with '_gpr' or '_prm_*' displayed. The problem now is that depending on the report I would now want to display some or all of the parameters that re not postfixed.

    DateRangeId
    DatePartId
    DataSetId
    RangeID_gpr
    TargetDate
    TargetDate_gpr
    PeriodFromTmp
    PeriodThroughTemp
    PeriodFrom
    PeriodThrough
    ClientId
    PortId
    ProjId
    EntityId
    PartnershipId
    PartnerId
    ContactId
    ClientId_prm_port
    PortID_prm_proj
    ProjID_prm_entity
    EntityId_prm_partnership
    PartnershipId_prm_partner
    EntityId_prm_contact

    Lyle us

    Thursday, June 11, 2009 11:34 PM

    This does in fact break in Reporting services 2008. Remoting cannot find field 'm_nameMap'

    I'm looking at it with reflector, and it appears similar. There is a internal hashtable m_nameMap but for some reason reflection can't find it.

    Bryan us

    Wednesday, July 01, 2009 10:52 AM

    Hi Beau, this procedure works great for me locally. One question I have: why are the report parameters listed first, and then listed with there values? Looking at the code I cannot tell. I would rather just show the parameters with there values if that is possible.

    The output of the procedure is:
    ReportParameters: Param1, Param2, Param3,... Param1: Param1 value1, param1 value2; Param2: Param2 value; Param3: Param3 Value

    I would like:
    Param1: Param1 value1, param1 value2; Param2: Param2 value; Param3: Param3 Value

    Also, I don't understand what you mean by using the reflector to find the value of the m_nameMap field. Can you dumb that down a little for me?

    Thanks

    Lyle us

    Wednesday, July 01, 2009 10:55 AM

    After further investigation, my problem appears to be a VISTA problem. There must be a security issue when it comes to reflection and Vista.

    After I deployed the DLL to our Windows Server 2003 server with SQL Server 2008, the reflection code starts working again.

    Tom Bell

    Sunday, July 19, 2009 11:52 AM

    I too was having the problem using the code at the top of this article once my report and assembly were deployed to the SSRS server.

    I had the thought that the problem was security related and added the following declaration above the method in my custom assembly:

    [PermissionSet(SecurityAction.Assert, Unrestricted = true)]

    I use C# and don't know if the syntax for VB is different or not but this instantly solved the problem for me.