.NET, SSRS, and SharePoint: Using .NET to display SSRS reports with custom Report Parameter formatting, and how to get it to work in SharePoint

In order to facilitate the display of SSRS reports in .NET web applications, Microsoft has included the ReportViewer control, which wraps the functionality of an SSRS Report and exposes methods and properties to specify, display, and manipulate it.  One definite annoyance of this control is that, by default, the way its ReportParameterInfo objects are displayed is not very aesthetically-pleasing.  If you are building a website for your company or for clients, you will probably not want such a bland presentation of these parameters.  There are a few ways to get around this default functionality.

Below I have described a solution that I used to dynamically create and bind custom .NET controls for each ReportParameterInfo object and tie them together with the ReportViewer.

Creating Your Own ReportViewer UI Controls

Report Parameters

First you’ll have to create your own ReportParameter controls to wrap the functionality of the .NET ReportParameterInfo objects and specifying the parameters to be hidden in the ReportViewer markup tag.

To do this, I created an abstract class called ReportParameterControl to contain all of the shared methods, and a few abstract methods to be implemented by the child controls.  It looks something like this:


public abstract partial class ReportParameterControl : UserControl
{

    public delegate void ReportParameterSelectedEvent(
             string reportParameterName, string selectedValue);

    public event ReportParameterSelectedEvent ReportParameterSelected;

    public abstract void BindData();

    public abstract void SetupDisplay();

    public abstract void ClearValues();

    public abstract string GetSelectedText();

    public void OnReportParameterSelected(string reportParameterName, 
        string selectedValue)
    {
        if (this.ReportParameterSelected != null)
        {
            ReportParameterSelected(reportParameterName, selectedValue);
        }
    }

    private void InsertParameterInfoIntoViewState(ReportParameterInfo info)
    {
        ViewState[string.Format(“ParameterInfo-{0}-Prompt”, 
          info.Name)] = info.Prompt;
        ViewState[string.Format(“ParameterInfo-{0}-State”,
          info.Name)] = info.State;
        ViewState[string.Format(“ParameterInfo-{0}-ValidValues”, 
          info.Name)] = GetDictionaryFromValidValues(info.ValidValues);
        ViewState[string.Format(“ParameterInfo-{0}-ValidValues”, 
          info.Name)] = info.Values;
    }

    private IDictionary<string, string> GetDictionaryFromValidValues(
                                                  IList<ValidValue> values)
    {
        if (values == null)
        {
            return new Dictionary<string, string>();
        }
        IDictionary<string, string> dictionaryToReturn = 
            new Dictionary<string, string>(values.Count);

        foreach (ValidValue validValue in values)
        {
            if (!dictionaryToReturn.ContainsKey(validValue.Label))
            {
                dictionaryToReturn.Add(validValue.Label, validValue.Value);
            }
        }

        return dictionaryToReturn;
    }

    public string ReportParameterName
    {
        get { return Convert.ToString(ViewState[“ParameterName”] ?? 
                    string.Empty); }
        set { ViewState[“ParameterName”] = value; }
    }

    public ReportParameterInfo ReportParameterInfo
    {
        set
        {
            this.ReportParameterName = value == null ? 
                string.Empty : value.Name;
            this.InsertParameterInfoIntoViewState(value);
        }

    }

    public string ReportParameterPrompt
    {
        get 
        { 
            return Convert.ToString(ViewState[string.Format(
                     “ParameterInfo-{0}-Prompt”, this.ReportParameterName)] 
                     ?? string.Empty); 
        }
    }

    public ParameterState ReportParameterState
    {
        get { return (ParameterState) ViewState[string.Format(
                “ParameterInfo-{0}-State”, this.ReportParameterName)]; }
    }

    public IDictionary<string, string> ReportParameterValidValues
    {
        get { return (IDictionary<string, string>) ViewState[string.Format(
                “ParameterInfo-{0}-ValidValues”, this.ReportParameterName)]; }
    }

    public IList<string> ReportParameterValues
    {
        get { return (IList<string>) ViewState[string.Format(
                “ParameterInfo-{0}-Values”, this.ReportParameterName)]; }
    }

}

(Notice that I used ViewState pretty heavily in this control.  If you will be using a large number of parameters, or your parameters will have a large number of Valid Values, you may want to look into using SessionPageStatePersister to avoid a very large page size.  If you are unable to use ViewState, you might have to get creative with hidden variables or Session.  Remember to keep page size and performance in mind)

I then created controls for each of the different kinds of parameter display types used for parameters in SSRS:

  • ReportParameterDropDownList – For single-select parameters with at least one valid value
  • ReportParameterCheckBoxList – For multi-select parameters with at least one valid value
  • ReportParameterDatePicker – For date parameters
  • ReportParameterCheckBox – For Boolean parameters
  • ReportParameterTextBox – For user-entered or default parameters

Each of these parameter controls implement ReportParameterControl and have their own methods for:

  • BindData – Used to bind the ReportParameterInfo information to the control
  • SetupDisplay – Used to initialize any other display information necessary
  • ClearValues – Used to clear the values from the specific ReportParameterControl

Dynamically Creating and Binding Report Parameters

After this, you’ll need a creative way to actually instantiate these ReportParameter controls and associate them with the ReportViewer’s ReportParameterInfo objects.  I just iterated through the ReportParameterInfo objects returned by ReportViewer.ServerReport.GetParameters() and dynamically created controls based on the parameter type:


private void CreateAndBindReportParameters()
{
    foreach (ReportParameterInfo reportParameterInfo in 
                ReportViewer.ServerReport.GetParameters())
    {
        //**This is discussed below**//
        if (reportParameterInfo.Dependencies.Count == 0)
        {
            ReportParameterControl parameterControl =  
                this.CreateControlFromParameter(reportParameterInfo);

            //Dynamically adds the control to the page
            this.AddReportParameterControl(parameterControl);

            //This step must come before SetupDisplay() and BindData()
            parameterControl.ReportParameterInfo = reportParameterInfo;

            parameterControl.SetupDisplay();

            parameterControl.BindData();
        }

    }
}

private ReportParameterControl CreateControlFromParameter(
    ReportParameterInfo reportParameterInfo)
{
    if (reportParameterInfo.DataType == ParameterDataType.Boolean)
    {
        //Create and return ReportParameterCheckBox
    }
    else if (reportParameterInfo.DataType == ParameterDataType.DateTime)
    {
        //Create and return ReportParameterDatePicker
    }
    else if (reportParameterInfo.ValidValues != null && 
                  reportParameterInfo.ValidValues.Count > 0)
    {
        if (reportParameterInfo.MultiValue)
        {
            //Create and return ReportParameterCheckBoxList
        }
        else
        {
            //Create and return ReportParameterDropDownList
        }
    }
    else
    {
        //Create and return ReportParameterTextBox
    }
}
private void AddReportParameterControl(
                 ReportParameterControl parameterControl)
{
    parameterControl.ID = Guid.NewGuid().ToString();
    pnlParameters.Controls.Add(parameterControl);
    parameterControl.ReportParameterSelected += ReportParameterValueSelected;
}

protected void ReportParameterValueSelected(string reportParameterName, 
    string selectedValue)
{
    IList<ReportParameter> parameters = new List<ReportParameter>();
    parameters.Add(new ReportParameter(reportParameterName, selectedValue));

    this.ReportViewer1.ServerReport.SetParameters(parameters);

    // **Logic that is similar to CreateAndBindReportParameters 
    //     for each dependent parameter** 
}

Cascading Parameters

The if-statement under the **This is discussed below** comment is to account for cascading parameters in SSRS.  As you may have noticed in the ReportParameterControl abstract class, we have an event and a delegate at the top that will take care of binding dependent parameters.  The logical flow of this data binding is as follows:

  1. We iterate through the ReportParameterInfo objects and create and bind all of the independent parameters.
  2. In the AddReportParameterControl method, we set our ReportParameterValueSelected method to be the event handler for each parameter’s ReportParameterSelected event.
  3. In the BindData method of each parameter that has other parameters that depend on it, we make sure to wire up an autopostback event (such as DropDownList’s SelecteIndexChanged) to fire.
  4. When we bind data and any time that the parameter value is changed we will handle the above event and fire the ReportParameterControl’s ReportParameterSelected event which will relay info with the value up to our page with our Report Viewer.
  5. Our ReportParameterValueSelected method is called, and we iterate through all of the dependent parameters and create and bind them using a method similar to the one used in CreateAndBindReportParameters.

There will be a button on the page to render the report.  When that is clicked, just iterate through the ReportParameterControl objects on your page, retrieve their names and values, and create ReportParameterInfo objects for them, and call the ReportViewer.ServerReport.SetReportParameters method with that collection of ReportParameterInfo objects.

Report Export (And issues with it in SharePoint)

The ReportViewer.Render method can be used to retrieve a byte array representing the report that you can then send to the user by calling the HttpContext.Current.Response.BinaryWrite method with it as a parameter.  This should work fine in most cases, but you will see issues with this in SharePoint.  Basically, using the response to write the bytes of the report to the user causes the SharePoint page that you are currently looking at to become unresponsive.

After looking into a lot of different options, I ended up with a solution.  I created a report download page that would initialize a report on page load and download the report to the user.  Then I wired up my download button to have a javascript onclick event that would open this page in a hidden iframe (style=”display:none”, not Visible=”false”) on my page without a postback.  The report downloaded fine without one of those tiny download windows that often pop up when you  use another page to launch your download.  There are a few caveats to this approach however:

  1. In order for your download button’s javascript to have all of the proper information for the report you are currently looking at, you need to re-add it’s onclick javascript event in your codebehind any time that the report you are looking at changes.
    1. This means any time you manually click “Update Report” you’ll have to re-add the javascript
    2. You will also have to handle the ReportViewer’s DrillThrough event and be sure to update the javascript when it is fired.
  2. Since the download page is rendered in a hidden IFrame, the user will not be able to see any errors that occur on the page unless you display them to the user in JavaScript.  Keep this in mind.
  3. You’ll have to figure out how you are going to pass your ReportParameters to your download page*.  You can pass them in the querystring if you don’t have many parameters, but if you have too many parameters, you may be forced to pass them in Session and clear them from Session as soon as you retrieve them in your report download page.

*An easy way to render the same report instead of passing parameters is using the SetExecutionId method of ReportViewer.ServerReport, but it can cause the report that you are currently looking at in SharePoint to become inactive if you change any of the report parameters.

Issues not covered in this post which should be decently easy to implement yourself are:

  • Creating a zoom control for zooming in and out on your report
  • A paging control for moving to the first/previous/next/last page in your report

Please let me know if you have any questions about the above code or if there is anything I missed.  Thanks.

ASP.NET User Controls: Simplicity, Scope, and Independence

It’s a great feeling when you are looking through the business requirements of an application that you need to develop and realize that there are large portions of code that you are able to reuse from another project.  It almost goes without saying that this scenario is only made possible by significant forethought when coding the first project.  This post is about applying that forethought specifically to ASP.NET user controls.

Simplicity

Judging by the controls that I have seen in various production environments, I’d say that the most important concept to keep in mind is simplicity.  In general, the more that you try to incorporate into a user control, the less reusable it becomes.

Let’s take, for example, a simple login control with a username and password textbox and a submit button.  This is a nice piece of standalone functionality that is a good candidate for abstraction.  Unfortunately, identifying good candidates for user controls is a lot easier than sticking to being simplistic in the implementation of them.

In the case of our login control, the implementation should be pretty vanilla. For example (C#):

public delegate bool AuthenticateMethod(string username, string password);

public partial class LoginControl : UserControl
{
    public event AuthenticateMethod Authenticate;

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
        if (!string.IsNullOrEmpty(txtUsername.Text)
            && !string.IsNullOrEmpty(txtPassword.Text))
        {
            if (this.Authenticate != null)
            {
                this.Authenticate(txtUsername.Text, txtPassword.Text);
            }
        }
        else
        {
            this.DisplayError("Username and password cannot be blank.");
        }
    }

    public void DisplayError(string message)
    {
        this.lblError.Text = message;
    }
}

As you can see, the control does some primitive error handling and, if the username and password aren’t blank, fires an event up to the parent page/control to handle the actual authentication logic.  It also exposes a public method that the parent can call to display any sort of authentication errors.

Note the absence of both authentication logic and code to redirect on successful authentication.  It seems like it makes our control a little plain, but it also makes it completely decoupled from our application-specific logic, allowing it to be reused easily.

Scope

Another attribute of reusable controls is the proper acknowledgement of what should and shouldn’t be in scope.  Just as in the simplicity example above, the fact that you can do more within your control doesn’t necessarily mean that you should.  A general rule of thumb is if an element or user control is declared in your control’s markup, your control should handle all of the initialization and event handling associated with it, but if not, leave it alone.

Based on my experience, one of the most common mistakes in user control development is trying to access or modify code that should be outside the scope of your control.  This includes, but is not limited to:

  • parent page/control elements
  • parent page/control hidden fields
  • cache, session, and query string variables that do not directly relate to elements within your control.

If you find yourself using

 this.Page.FindControl("somePageControl") 

at all, you are definitely accessing something that shouldn’t be within the scope of your control.

The reason that this is a bad practice is that not only does it make your control dependent on the parent page having a control named “somePageControl,” which makes it a lot less reusable, but it also can make debugging a nightmare.  It’s funny how while coding we often have no trouble disregarding scope best practices, but when debugging, we are baffled that our control/session/cache variable magically changes without our page’s codebehind modifying it.  When we find ourselves starting to slip on these scope best practices, it’s time to take a step back and think about the interaction between our control and its parent control/page.

An easy way to think of this interaction is like any other parent-child relationship.  Our login control from the simplicity section does not tell its parent what to do or tinker with things its parent owns.  Instead it fires an event up to the parent, notifying it that something has occurred that it may want to consider taking action on.

Our login control also exposes a public method to allow its parent to modify it when its parent gets new information that our control may not know about.  To reduce dependence on the presence of specific parent elements, session variables, etc., a control should expose public methods that allow its parent to pass it the information needed to perform the functions the parent asks of it.

This is the way communication should occur between controls and their parents.  If you find that your control is starting to step outside of its boundaries in terms of what it modifies, create an event that its parent handles, and let the parent modify it’s own objects if it so chooses.  Likewise, if it starts to access objects or primitives that it shouldn’t, create public methods that allow the parent control to pass these values to its control.

Independence

Going right along with our Scope section is the idea of independence.  As you can see by the communication model described in the previous paragraph, our login control is completely independent from its parent control/page.  It handles all of its own events, and no matter how the parent is structured, it can’t cause our control to error at runtime.  If it doesn’t assign an event handler for our event, it’s fine; we won’t fire the event.  If our control’s parent doesn’t properly authenticate the user, that’s not its problem; it gives its parent all of the necessary information, and from there, it’s the parent’s job to take care of the rest.

Though the concepts of simplicity, scope and independence are very related in the case of user controls, independence is probably the most important idea when it comes to control reusability.  If there is any sort of dependence on session variables, parent elements, etc., our control limits the number of scenarios in which it could potentially be used.

Keep these three concepts in mind when creating your controls, and you will surely save yourself plenty of time in your future coding endeavors.

Hello World!

Welcome to my technical blog!  It’s nice to finally be up and running.  Over the past few years, I have had the opportunity to work for many different companies in a variety of different industries.  In each case, I have been asked to either add functionality to a current system or create a new solution that will undoubtedly be added to and grow as the company evolves.  Though the approach to each of these types of projects can be very different, there are many similarities, such as technologies used and general development methodology and best practices.

In this blog, I plan to discuss the different technologies used and the different development practices that I see in my professional travels.  I will list the methodologies that I have seen to be most successful in terms of scalability, maintainability, and flexibility; practices that I will avoid moving forward; and reasons behind why they fall into each category.

Follow

Get every new post delivered to your Inbox.