Home   |  Articles   |  Resources   |  Humor   |  Feedback       

  Login   Register 

Ads Via DevMavens


SQL Reporting Services Tree Navigation Sample using a Web Service API

Posted by on Sunday, February 22, 2004

Demonstrates using Microsofts SQL Server Reporting Services, the Web Service API and the Report Viewer to build a treeview of available reports

When you start thinking about integrating Reporting Services into an application, one question that might arise is how to display a list of available reports to a user. Although Reporting Services allows a user to browse through reports on the report server (see the figure below), chances are you will want to provide a snazzy custom user interface and possible apply additional logic before presenting the end user with a list of reports.

Unfortunately, this demonstration isn’t exactly snazzy looking, but it will show you how to use the web service API to pull back a list of reports and display them in a tree control, as shown in the next screen shot.

This sample uses the Tree View from the Internet Explorer Web Controls package. You can download the free control from Microsoft’s ASP.NET site. Follow the instructions included with the package to install the Tree View control into your Visual Studio .NET toolbox.

The complete source code for this project is available here. At the bottom of this article you can find a list of resources  to cover some of the topics (impersonation, web service proxies) that we use but do not explain in detail.

Create The ASP.NET project

The first step is to create a new ASP.NET web application project. We’ve renamed the Webform1.aspx file the wizard provides to the name ListReports.aspx. As for the user interface, all we will need on the form is a tree control. The ASPX contents look like the following.

<%@ Page 
    language="c#" 
    Codebehind="ListReports.aspx.cs" 
    AutoEventWireup="false" 
    Inherits="RsIntegration.ListReports" 
%>
<%@ Register 
    TagPrefix="iewc" 
    Namespace="Microsoft.Web.UI.WebControls" 
    Assembly="Microsoft.Web.UI.WebControls" 
%>
<HTML>
    <HEAD>
        <title>ListReports</title>
    </HEAD>
    <body>
        <form id="Form1" method="post" runat="server">
            <iewc:TreeView id="TreeView1" runat="server"/>
        </form>
    </body>
</HTML>

Setting Up The Web Service Proxy

Before we can populate the tree control from the ListReport code-behind, we will need a proxy to invoke Web Service. In the VS.NET 2003 IDE, select the Project menu and click on ‘Add Web Reference’. In the dialog box shown below, enter the path to the ReportService.asmx file where Reporting Services is installed.

Once the IDE has located the service you can change the Web Reference name to a more descriptive entry, in the dialog above I use ReportService. This name will become the namespace for the web service proxy.

Global Constants

I found it useful to define a few constants in this project to keep track of URL and report paths. I added the following to the Global class in global.asax.cs.

// Report Server defines the URL to root of the 
// Reporting Services home page. 
public static string ReportServer
{
    get { return "http://reporting/reportserver"; }
}

// ReportPath, when appended to the ReportServer property, 
// will define the root of the report search. 
// For example, to view all available reports on the report 
// server, use "/" as the ReportPath. Setting the value to 
// "/MyDemoReports", would only show reports and subdirectories
// under http://ReportServer/MyDemoReports. 
public static string ReportPath
{
    get { return "/SampleReports"; }
}

// There are a couple places where we need to do string 
// manipulation to add and remove path seperators - 
// sometimes the seperator needs to be passed as an array
// of char, othertimes as a string. These two properties
// simply define both so the code is a little cleaner
// when we do the string munging. 
public static char[] PathSeparatorArray
{
    get { return pathSeparatorArray; }
}

public static string PathSeparatorString
{
    get { return pathSeparatorString; }
}

protected static char pathSeparator = '/';
protected static char[] pathSeparatorArray = { pathSeparator };
protected static string pathSeparatorString = new string(pathSeparator, 1);	

The logic behind ListReports.aspx

With everything in place, we can begin to build our tree control. Let’s first take a look at the Page_Load event handler.

private void Page_Load(object sender, System.EventArgs e)
{
    if(!Page.IsPostBack)
    {
        ReportingService rService = new ReportingService();
        rService.Credentials = System.Net.CredentialCache.DefaultCredentials;
        CatalogItem[] catalogItems;
        catalogItems = rService.ListChildren(Global.ReportPath, true);
        BuildTree(catalogItems);
    }
}

First, we instantiate our web service proxy. The upcoming web service call requires authentication, and CredentialCache.DefaultCredentials holds the credentials for the current security context. (Note, in the web.config file for this application we are using %lt;IDENTITY impersonate="true" />to execute with the identity of the client. For more information, see resources at the end).

Reporting Services provides the ListChildren API to find out what is in a given folder on the server. The first parameter is the path to the folder to look in, the second parameter is a flag to indicate if the server should recurse through subdirectories. Since we want to avoid making multiple web service calls, we will ask the server to recurse and return everthing we need on one invocation.

The web service call will return an array of CatalogItem objects. (Note: only items the user has permission to view will appear in the result). A CatalogItem object exposes a number of properties with information about the child item, for example, Description, CreationDate, and Size. We will some of these properties to work in the BuildTree method.

private void BuildTree(CatalogItem[] catalogItems)
{
   foreach(CatalogItem item in catalogItems)
   {
      if(item.Type == ItemTypeEnum.Report)
      {
         string path = item.Path.Remove(0, Global.ReportPath.Length);
         string[] tokens = path.Split(Global.PathSeparatorArray);
         BuildNodesFromTokens(tokens, 0, TreeView1.Nodes);            
      }
   }
}

In BuildTree we iterate through the array of CatalogItem objects to pick out just the reports. In addition to reports, the array of CatalogItem objects will contain objects representing data sources, sub folders, and resources, to name a few. Inspecting the Type property of a CatalogItem allows us to work with just reports.

From here, the rest of the logic in this page works to build the tree. To make the algorithm easier, we strip the parent directory from the CatalogItem object’s path, and then split the remaining part of the path by the path separator. The Path property for the ‘Company Sales’ sample report will return ‘/SampleReports/CompanySales’. What we pass to the next method is an array of strings containing 2 elements : “SampleReports” and “CompanySales”.

The BuildNodesFromTokens method , shown next, walks down the array of tokens to build tree nodes where needed. The most important part of the method is where we build a TreeNode to represent a report. We need to set the NavigateUrl of these nodes so the user can click to go to the report page. The destination will be the RenderReport.aspx page we build in the next section. RenderReport.aspx will need to know what report the user requested, so we will reconstruct the path to the report by concatenating the tokens together and passing the result on the query string. The NavigateUrl property for the Company Sales TreeNode should come out looking like so: "RenderReport.aspx?Path=SampleReports%2fCompany+Sales”

private void BuildNodesFromTokens(string[] tokens, int index, 
					TreeNodeCollection nodes)
{
    TreeNode node = null;

    // first, see if a node for the current token
    // exists on this level
    for(int i = 0; i < nodes.Count; i++)
    {
        if(nodes[i].Text == tokens[index])
        {
            // a node was found at this level, 
            // no need to continue searching
            node = nodes[i];
            break;
        }
    }
    
    // no node was found, will need to 
    // create a new tree node
    if(node == null)
    {
        node = new TreeNode();
        node.Text = tokens[index];
        nodes.Add(node);

        // check if this is the final token, which means
        // this token represents a report name
        if(tokens.Length -1 == index)
        {                                     
            // set the navigation property to 
            // allow the user to click the report name
            // and navigate to see the report
           node.NavigateUrl = "RenderReport.aspx?Path=" + 
              Server.UrlEncode(
                   String.Join(Global.PathSeparatorString.ToString(), tokens)
              );
        }
    }

    // if we have not reached the end of the token list,
    // increase the level by (move down the tree) and 
    // call ourselves again
    index++;
    if(tokens.Length > index)
    {
        BuildNodesFromTokens(tokens, index, node.Nodes);
    }
}

The Report Viewer

The RenderReport page we will build requires the report viewer WebControl provided in the Reporting Services samples. You’ll need to first build this sample solution which appears in the Reporting Services\Samples\Applications\ReportViewer\cs (or vb) directory underneath your Reporting Services installation. Open the project in Visual Studio .NET and build to produce the reportviewer.dll

Back in our solution, add the ReportViewer control to the Toolbox by right clicking and selecting “Add/Remove Items”. Browse to the reportviewer.dll assembly you created above and the new WebControl should appear in your Toolbox for web forms.

Render Reports

The RenderReport.aspx page has a fairly simple job. After dragging the ReportViewer component onto the page and adding some other window dressings, the ASPX contents should look similar to the following listing.

<%@ Register 
      TagPrefix="cc1" 
      Namespace="Microsoft.Samples.ReportingServices" 
      Assembly="ReportViewer" 
%>
<%@ Page 
     language="c#" 
     Codebehind="RenderReport.aspx.cs" 
     AutoEventWireup="false" 
     Inherits="RsIntegration.RenderIFrame" 
%>
<HTML>
    <HEAD>
        <title>RenderIFrame</title>
    </HEAD>
    <body>
        <form id="Form1" method="post" runat="server">
            <h3>OdeToCode RenderReport Page</h3>
            <cc1:ReportViewer 
                   id="ReportViewer1" 
                   runat="server" 
                   Width="600px" 
                   Height="400px">
            </cc1:ReportViewer>
        </form>
    </body>
</HTML>

The logic for the page simply needs to set some properties on the ReportViewer. The ReportViewer, if you look at the source, is implemented as an IFRAME element and exposes properties to point the IFRAME src attribute to the correct report. We simply need to fetch the path parameter from the query string to build the complete path to the report.

if(!Page.IsPostBack)
{
    ReportViewer1.ServerUrl = Global.ReportServer;
    ReportViewer1.ReportPath = Global.ReportPath + Request["Path"];
}

Conclusion

There will be a number of ways to integrate Reporting Services into new and existing applications. In this article we used a web service API to build a tree control of available reports. There are additional web service APIs to render reports which would give you greater flexibility in the appearance of the final report – the subject for a future article.

 

by Scott Allen

Login to Download code

Resources

ASP.NET Impersonation

PRB: "Access Denied" Error Message When You Call a Web Service While Anonymous Authentication Is Turned Off 

Adding and Removing Web References

INFO: Troubleshooting Add Web Reference Issues


Comments:

One burning question
By tammywoods on 3/8/2004
This example is great and has cleared up a few issues that I had, however, I have one more burning question.... how do we pass parameters to these reports from our snazzy custom interface?

Good question
By scott on 3/8/2004
The short answer is that the report server will accept parameters in the URL as long as you follow the expected format (see books on line). The long answer will hopefully appear in a new article soon.

unable to load Microsoft.Samples.ReportingServices assembly
By sushiln7 on 3/10/2004
I build the sample in Services\Samples\Applications\ReportViewer\cs reportviewer.dll.
After dragging this control on the asp.net page & viewing the aspx page in browser i am getting the error "unable to load Microsoft.Samples.ReportingServices assembly"

sushil
sushil7_n@yahoo.com

Retrieving the dataset associated with a parameter using Web Service
By geoman10 on 4/27/2004
Do you know of anyway of retrieving the dataset (or even the query text) that is associated with a parameter?
I want to create my own user interface that will use this dataset information. This is done for two reasons one the datatimepicker on a windows application is nice, and two reporting services orders the parameters automatically and doesn't allow nice grouping.

Re: unable to load assembly
By scott on 4/27/2004
Hi Sushil: I responded to you via email some time ago but have not heard back, I hope everything is working for you now. I gave a number of items to double check: make sure the @Register declaration is correct for the assembly, make sure the assembly appears in the solution explorer under the References node, and ensure "copy local" is true for the assembly in the properties window. This all assumes the assembly correctly built in the RS sample directory.

Re: parameter DataSet
By scott on 4/27/2004
Hi Geoman10,

I did a small write up on a possible solution here: http://odetocode.com/Blogs/scott/archive/2004/04/27/230.aspx. What you are asking is possible, I hope the blog entry helps out.

SQL Server Authentication from Reporting Services
By posadas on 5/3/2004
Hi,
I have some reports uploaded into the Report Manager and can access them if I type the user and pwd. However,in my web app, I have several links that point to several reports and my users need to enter their credentials every time.
They have been authenticated already when entering the website, now, I want to use their credentials rather than asking them to enter it again for each report they want to see.
Thus, how can I do this programmatically?

Thank you in advance

Juan

Credentials
By poonam on 5/3/2004
There are a couple of things you can do depending on your application.
1. When you define your datasource you can use generic credentials, and save the user id and password within the data source. Remember to check the 'Allow Saving Password' checkbox. A potential problem with this solution is that anyone can gain access to your reports.
2. Alternatively you can use Windows NT Integrated security. Using Windows credentials would enable access to data based on the users’ security setting. A potential problem in this case would be scheduled reports – since a user would not be logged on to the Report Server when Scheduled reports were run, the users Windows credentials would be unavailable.
3. If your reports are embedded within a web or windows application, then you can use impersonation. This can be done in the web.config or in code.

RS WS API
By posadas on 5/4/2004
Thank you for your comments. As you mentioned embedding the credentials into the datasource is not a good idea, however, the simplest.

I am using SQL Server authentication for my app and my users are outside of the domain, although still inside the company. Therefore, any windows authentication will not work.

I'd like to see a clear example of how I can manipulate getting a report to the browser progammatically. Manipulating the datasource and adding the credentials "on the fly"

Juan

How to control paging in reporting services?
By Suganthi on 7/14/2004
Reporting services support built-in paging. I eed to have a control over paging (I want to display n rows per page in a report). How can this be achieved?

List and Report on Same page
By Iain on 7/21/2004
Hello,
I have the list to navigate the reports, when you click a report it opens it on a blank page. I just wondered if I could open the report on the same page. So I have the list on one side and the current report on the other.
Thanks
Iain

Help
By znguessan on 7/22/2004
HI, i have a pb when i tried to test the example.
i recieved the error msg : The request failed with HTTP status 401: Unauthorized.
I dont know what action to do.

reporting service in tree
By gassiep on 7/23/2004
Can you help me out, I get this error with your example:

Parser Error
Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately.

Parser Error Message: Could not load type 'RsIntegration.Global'.

Source Error:


Line 1: <%@ Application Codebehind="Global.asax.cs" Inherits="RsIntegration.Global" %>


Supplying credentials
By JasmineRose on 7/28/2004
Hello Jaun,

have you come up with any kind of solution, I too need a solution for the same senario.

Regards,
Jasmine

Parameters
By cferrer on 7/29/2004
Hi, I need know the following:

How do I send any parameters through an URL in reporting services? I don't know how to do that.

Credentials
By trexdino on 8/10/2004
private void Page_Load(object sender, System.EventArgs e)
{
if(!Page.IsPostBack)
{
ReportingService rService = new ReportingService();
rService.Credentials = System.Net.CredentialCache.DefaultCredentials;
}
}

What tree?
By wolomolo on 9/3/2004
Hi, I downloaded the code and set it up on my test machine. My problem is I only get the reports listed in a long row, they are not listed as a tree.

I probably missed out on some vital part, maybe it is the ListTree properties that have to be adjusted?? Would really appreciate some help.

Best regards,
Johan Friberg

Request of Help to transform in object folder
By andretna on 9/23/2004
I would Like to know how to do the same alone functionality that instead of object tree using objects folder.

Thank you,

André

I need help to show the reports with the object folder
By andretna on 9/24/2004
I would like to show the reports without using the administration of ReportServer, the vision would be very similar, but the bars and buttons that exist in the administrator would not appear. Can anybody help myself?

Thank you.

André Albuquerque

RS WS API
By ImraneA on 11/1/2004
Hi there

I have the exact problem ! Where u successful in solving this problem ? Any advice to a fellow developer...

ImraneA

Report Server as a web service
By kmathieu on 12/2/2004
The tree structure was interesting. However, we want to use the report server as a web service and I notice that the tree structure includes URLs. Our users will not be able to log into our report server but will access the reports through another application via a web service. How can I accomplish this?
Also we will be using the web service from a Java application so will not have the .NET library.

Reporting Services
By cxmohan on 12/14/2004
Hi,

I am getting the following error while executing program to create Subscription.

"Additional information: An attempt has been made to use a delivery extension that is not registered for this report server. --> An attempt has been made to use a delivery extension that is not registered for this report server."

Please help me..

Thanks,
Mohan

Build Errors
By a222493 on 1/31/2005
I'm kinda new to VS.Net, so forgive me if this is an elementary question

I downloaded the source code, unzipped and added the existing project to my solution.

When I build the solution, I get the following errors:

The type or namespace name 'Web' does not exist in the class or namespace 'Microsoft' (are you missing an assembly reference?)
The type or namespace name 'Web' does not exist in the class or namespace 'Microsoft' (are you missing an assembly reference?)

The type or namespace name 'TreeNodeCollection' could not be found (are you missing a using directive or an assembly reference?)
The type or namespace name 'Samples' does not exist in the class or namespace 'Microsoft' (are you missing an assembly reference?)

The referenced component 'Microsoft.Web.UI.WebControls' could not be found.
The referenced component 'reportviewer' could not be found.

Copyright 2004 OdeToCode.com 


The Blogs
Subscribe to the OdeToCode blogs for the latest news, downloads, new articles, and quirky commentary.
New Articles
Databinding in Silverlight
This article will cover data binding features in Silverlight, including binding expressions, validation, converters, and binding modes.

The Standard LINQ Operators
This article will cover the standard LINQ operators provided by LINQ for filtering, grouping, joining, converting, projecting, and more.

C# 3.0 and LINQ
C# 3.0 introduced a number of new features for LINQ. In this article we'll examine the new features like extension methods, lambda expressions, anonymous types, and more.

Most Popular Articles
Table Variables In T-SQL
Table variables allow you to store a resultset in SQL Server without the overhead of declaring and cleaning up a temporary table. In this article, we will highlight the features and advantages of the table variable data type.

ASP.Net 2.0 - Master Pages: Tips, Tricks, and Traps
MasterPages are a great addition to the ASP.NET 2.0 feature set, but are not without their quirks. This article will highlight the common problems developers face with master pages, and provide tips and tricks to use master pages to their fullest potential.

AppSettings In web.config
In this article we will review a couple of pratices to keep your runtime configuration information flexible.

Contribute Code
Privacy
Consultancy