.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:
- We iterate through the ReportParameterInfo objects and create and bind all of the independent parameters.
- In the AddReportParameterControl method, we set our ReportParameterValueSelected method to be the event handler for each parameter’s ReportParameterSelected event.
- 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.
- 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.
- 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:
- 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.
- This means any time you manually click “Update Report” you’ll have to re-add the javascript
- You will also have to handle the ReportViewer’s DrillThrough event and be sure to update the javascript when it is fired.
- 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.
- 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.