Saturday, September 10, 2005

 

Reporting over SharePoint lists with MS Reporting Services

Update: I have written an extended version of the example described in this article. If you plan to work with the solution described here, check out the newer version.

This article is a translation of my article in Dutch that appeared in issue #9 of .NET Magazine. The PDF version of the Dutch article can be found at the Microsoft site. All code can also be downloaded from the Microsoft site or here. If you want to copy the code in this article, be aware that some characters may not be what they look like.

As part of Microsoft's work to become a major player in the Business Intelligence marketplace, it introduced Microsoft Reporting Services in 2004.. This reporting engine is freely available for owners of a SQL Server 2000 license. It allows organisations to create WYSIWYG reports combiining several sources like OLEDB databases and OLAP cubes. Reporting Services offers a broad range of target formats (such as HTML, PDF and MS Excel) and several delivery methods (like a web interface or scheduled e-mails). Designing and testing of reports is done within Visual Studio. All this allows the typical Microsoft developer to use Reporting Services to quickly create impressive reports in a well-known and comfortable environment.

Of course, most organisation posess more information than that stored in relational databases. For example: many organisations use SharePoint lists to keep track of critical business processes. Think of project teams administering status and priority of all tasks and subtasks within the project in a Tasks list. One would like to include that data into reports as well to allow the management to get an overview of the progress presented in a graph. Luckily, the architecture of Microsoft Reporting Services has several extension points, so we can create a solution for this requirement by building a Reporting Services Extension. How to do that is the subject of this article.

SharePoint Extensibility

An extension to Reporting Services that can report about data in SharePoint lists should at least be able to get access to this data in SharePoint. This turns out to be relatively easy, as Windows SharePoint Services (WSS) exposes a broad range of its functionality as SOAP web services. In our case, we are only interested in the SOAP service Lists.asmx. This services exists for every WSS site at the URL http://servername/sites/sitename/_vti_bin/Lists.asmx. The service exposes methods such as GetListCollection (returns all available lists in the site), GetList (returns detailed information about the list and its columns) and GetListItems (returns the full content of the list). The result of all of these methods is an XmlNode object (representing the type from the WSDL). Getting the right information from this XML structure can be tiresome, but all of the available information on the list can be retrieved through this interface.

Reporting Services Extensibility

The Reporting Services architecture provides extension point at several places. This is all done through a plug-in model. The extension is an assembly containing .NET types implementing specific interfaces. Reporting Services works only through these interfaces. Which concrete type is instantiated depends on configuration and options selected by the user.

How it works

To report over the SharePoint lists, we will write a Data Processing Extension. But before we delve into the code, let's have a short look at the way the Reporting Services engine will use our extension. The pattern used is very much like that of DataReaders in ADO.NET. A Data Processing Extension contains classes implementing at least the following interfaces (all from the namespace Microsoft.ReportingServices.DataProcessing): IDbConnection, IDbCommand and IDataReader. There are sevral other interfaces that Data Processing Extension can implement when appropriate, but these three are the core.

Image 1: Collaboration diag for RS, IDbConnection, IDbCommand en IDatareader
As can be seen in image 1, the Reporting Services engine first creates an instance of IDbConnection. On this instance, it sets a number of properties (eg. ConnectionString) and then calls the methods Open and CreateCommand. The Connection object then returns an instance implementing IDbCommand. This Command object features CommandType and CommandText properties as well as an ExecuteReader method. This method returns an object implementing IDataReader. This object is very much like an ADO.NET DataReader. The forward-only reader is used by reporting services to pull out all the data it needs for the report.

We will now have a look at some relevant code snippets from the central classes of our extension. The full working source code can be downloaded from www.microsoft.nl/netmagazine9.

Connection

The Connection class implements IDbConnection and is Reporting Sevrices' starting point when using our extension. Of the properties and methods from IDbConnection, a number is not supported bij our extension. That's why for example BeginTransaction will always throw NotImplementedException and the ConnectionTimeout property always returns 0. Transactions and timeouts are not really relevant for our extension, because we do not have a "real" database connection in use. That is why so little happens in the Open and Close methods:

The one property in Connection that is important is ConnectionString. The connection string for this extension can contain only one item: the URL for the WSS site containing the data. To make later elaboration possible, we will still use the rather complex of the OLEDB connection strings: NAME1=VALUE1;NAME2=VALUE2. The different fragments in the connection string are stored in a Hashtable for later retrieval. The WssSiteUrl property is used to extract the correct fragment from the hashtable.

Besides implementing the IDbConnection-interface, the Connection class also implements the IDbConnectionExtension interface. This interface contains four properties the deal with security and login credentials: Impersonate, IntegratedSecurity, UserName and Password. By implementing this interface, we signal that our extension is capable of using this information in building an appropriate connection to out data source. In our case, we will be calling web services and pass the ligin information to the WSS webserver. The Connection class itself does nothing with the properties. The information they hold will later be used by the Command object. The CreateCommand method does nothing but returning an instance of the correct Command type.

The LocalizedName property allows us to specify how our extension will show up in the list of available source types. We always return the text "SharePoint Lists".

Command

The Command object, which is returned by the CreateCommand method, is the real core of our solution. The object can, based upon the Connection object and the name of a SharePoint list, fill an ADO.NET DataTable with all of the items in the list. Then it can instantiate a DataReader object that feeds the data from this DataTable to the Reporting Services engine.

The most important properties in the IDbCommand interface are CommandText and CommandType. When reporting on relational databases, CommandText will normally contain an SQL query.In out extension, we use the CommandText property to pass the name of the SharePoint list. Of the three existing CommandType values, we only support CommandType.Text:

Our solution does not support parameters and transactions. So, the Parameters property always returns an empty collection and the Transaction property is always null, while the CreateParameter method throws a NotImplementedException. The CommandTimeout property in this solution always returns 30 (for 30 seconds).

The real work of Command happens when the ExecuteReader method is called. This in turn calls GetDataTableFromWSS and passes the resulting DataTable to a new DataReader:

The GetDataTableFromWSS is the workhorse of the solution. The following code fragment shows the full source of this method.

So what happens in this code? It first instantiates a proxy class for the web service. It then sets some properties based upon proerties of the Connection object. Then is calls the web method GetListCollection. This method returns us an XML structure of information about all lists. The only thing we are after is the Name of the list, which is a Guid we will need in all of the web service calls later on. The XML returned by GetListCollection looks like this:

Now that we know the unique name of the list, we call the GetList method. The resulting XML contains information about names and types of all columns in the list:

For each column in the WSS list, we add a new field to our DataTable with the same name. There is one nasty detail: WSS uses both a Name and a DisplayName property. When a column in WSS is created, these are identical, but DisplayName can be changed, while Name is fixed. SharePoint shows only the DisplayName in its UI, so it is important that we follow these names in the report designer (people working with the WSS list will be blissfully unaware of the real immutable names). So we use the DisplayName as column name in our DataTable and have to keep track of which Name belongs to which column name (we do this in a hashtable). Also, we build an XML fragment containing a reference to each column in a StringBuilder. We will use this XML string later to indicate that we want to retrieve all available fields from the list.

When we only have to return the schema - this happens when the extension is used by the report designer - we are ready now, but normally, we also need the data itself. We will now load this data into the DataTable using the web method GetListItems. When you call this method, it returns an XML document like this:

Note that all fields in the list have been converted to attributes by prefixing their names with "ows_". We loop through all z:row elements and for each of the columns in our DataTable, we check if a matching attribute on the row is available. We find the name of the attribute by combining the fixed prefix with the Name we stored for each column in a hashtable. The string returned from the GetAttribute method can be stored in the DataTable without converting or casting, because DataTable internally always performs a Convert on the data entered.

For readability of the reports, we do change the values of fields of type Choice and of references to other SharePoint lists. For these fields the value returned by the web method is of the form "36;#Teun Duynstee". It featurs first the numeric ID, followed by ; and # and then the human readable presentation. In reports, we are rarely interested in ID's at all, so we use a regular expression to strip ID and separator from the value before placing it in the DataTable. The DataTable with content is then passed to a DataReader which is then returned as the result of ExecuteReader.

DataReader

The DataReader is a rather simple class. It is not much more than a wrapper around the DataTable created and filled by the Command object. Internally, an Enumerator is instatiated and used by the CurrentRow property to return the current row.

This CurrentRow property is then used by all methods defined in the IDataReader interface, like GetFieldType, GetValue, etcetera.

Deploying the solution

Once the plugins are built, they can easily be registered by adding a reference in a config file. This must happen in two separate places: one for the report designer on the machine where reports will be build and one for the service on the machine that will eventually produce the reports. The dll file we built must also be copied to two different directories. The steps for deployment are (the directory paths assume a default install):

On a machine that does not have Visual Studio.NET installed, the report designer is of course not available.

Results

Now that we installed the extension, we will try to create a report based upon some information in a team site. In this case, we will take the Tasks list, which is available by default in any project team site. We create a new project in Visual Studio.NET 2003 using the Report Project Wizard.

Image 2
In the wizard, we see the new extension appear right away: Image 2 displays the wizard step that configures the data source. In the list of data source types, the option "SharePoint Lists" has appeared (this is the value returned by Connection.LocalizedName). Our connection string contains only one element: the team site URL. Using the Credentials button we can set the account that should be used to retrieve the data from WSS. In the next wizard step, the Command text can be entered. We enter "Tasks", the name of the list we want to query. Next we will see the screen in Image 3.

Image 3
In the list of available fields, we see all of the Fields the Task list features. There are actually more fields than you would normally see in SharePoint. The list also contains the invisible and calculated fields. If you want to, you can use these in your reports as well. Then, we just create a simple report by selecting the fields and adding them to the report and we can easily create a report as in Image 4.

Image 4

Conclusion

As both Windows SharePoint Services and MS Reporting Services have been designed with extensibility in mind, it is not very hard to connect the two into one solution. Web parts are available to show reports from Reporting Services inside WSS sites. Using the technique shown in this article, you cn present the information available in your WSS sites through Reporting Services. As organisations increasingly organize some of their primary processes through WSS team sites, the importance of reporting over the progress of these processes will only grow.

Postscripts

Some readers have sent me questions. I have encountered limitations myself. I have (and will in the future) written about these on my blog. These are the most relevant posts:

How to filter the results from WSS in your report Setting CAS to use our Reporting Services data extension

Labels:


Comments:
Thanks for a step-by-step guide. Can we access multiple wss lists in one report, and what kind of queries can we write against the wss list columns?

Regards
Satish
Compusol
 
In my list I use mutli-line fields, but when they are displayed in Reporting Services, I get "div" tags with these. Is there any way to avoid/remove those tags?

Thank you very much for this solution - its been a lifesaver!

Ian
 
@Satish: yes, you can.

@Ian: For now, I cannot help you with that. The multi-line fields are actually containing HTML markup. I am working on an enhanced version of the tool that will include a "StripHtml" feature. But I can make no promises on when I will publish this.
 
I'm glad you've got it in mind... maybe you'll sneak it out one day.

Again, thank you for at least providing the method for reading it!
 
Hi,
Grate article !

I'm having a problem,
I followed your step-by-step ,but
when trying to create a new DataSource in the
report manager site, the new ext not showen in the dropdown.

on the VS designer it work just grate !

what can be the cause of that and how can i overcome it.

Thanks,
LirLir
 
@LirLir: this should work. Check if you updated both config files on the server (both in the ReportServer and in the Report Designer folders)
 
Thanks for your reply!!
my designer and reportserver are on different servers.
on the designer it all working just fine.

on the report server i config it like you worte:
i add this line in the DATA element
in the file:
C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\RSReportServer.config

Extension Name="SPSLIST" Type="Macaw.ReportingServices.SharepointListExtension.Connection, Macaw.ReportingServices.SharepointListExtension"/
----------------
in the file:
C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\rssrvpolicy.config

i add the CodeGroup to the end of the PolicyLevel Element look like that:
CodeGroup
class="UnionCodeGroup"
version="1"
PermissionSetName="FullTrust"
IMembershipCondition
class="UrlMembershipCondition"
version="1"
Url="$AppDirUrl$/bin/Macaw.ReportingServices.SharepointListExtension.dll"
/
/CodeGroup
-----------------
And also added the DLL in here:
C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\bin\Macaw.ReportingServices.SharepointListExtension.dll


WHATE AM I DOING WRONG ?

(I execlude the tags from my reply)
LirLir
 
LirLir,
Your config settings look fine to me. On my test system, the Sharepoint Lists option appears in the "Connection Type" listbox when I cretae a newDataSource. You may have to do a iisreset. Otherwise, check your event log. I'm out of clues.
 
Hip-Hip....
I found whate went wrong ! Dll didn't have correct Security configuration on the folder.
Now i can see the SPSList option in the dropdown ,BUT i getting this error messege , with no explanation:
An internal error occurred on the report server. See the error log for more details. (rsInvalidReportServerDatabase) Get Online Help
?


do you have any idea how to solve this problem ?

Thank you so much for your help i realy appreciate it !!!!


LirLir
 
I succeed, i re-installed the SP2

Thank for all your help.

LirLir
 
Hi Tuen
thanks for this article it has helped to clear up a lot for me - I was originally going to write an SQL Query to read directly from the Sharepoint Content Database!

I am having some difficulties though. I copied the dll to both the ReportServer & ReportDesigner directories. In Visual Studio.NET 2003 I can start the Report Wizard that allows me to setup my Connection String. However, I get an error saying that "a connection cannot be made to the database"

So I skipped VS.NET2003 and tried to create a shared connection in Report Manager, but the new connection type didn't appear in the dropdown. I read the post by LirLir, so I checked the security Levels and they seem fine. I checked the event viewer and there is an error: Report Server cannot load the SPSList extension.

How would I go about discovering the cause of this error?
 
@Jason: If you have your Code Access Security set up right (http://dotnetjunkies.com/WebLog/teund/archive/2005/06/14/121258.aspx) and your dll deployed to the right directory and the config files correctly edited, it really should work. The VS environment is usally the easiest to get to work.
 
Hi Tuen,
Thanks for the reply. I have yet to get this working. I have Sharepoint and SQL Reporting Services installed on the same machine. Then I have Visual Studio and SQL Reporting Services Client Tools on my Dev Machine.

I'm trying to get it working on my Dev Machine, and I get as far as selecting a Data Source in Visual Studio. I set the ConnectionString to be "site={servername}\{sitename}", I click the Credentials button and say use integrated security and then hit OK and I get a "A connection cannot be made to the database. Set and check the connection string."

- I have copied the dlls to both the sharepoint server and my dev machine
- I have updated the config files to point to the dll
- I have added a new policy to the rssrvpolicy.config on the sharepoint server
- I have done an iisReset

I'm at a loss basically
 
sorry, I just realised I've been spelling your name wrong
 
I'm an idiot. I updated my config file in the right place, just incorrectly!

Thanks for all your help Teun, and for this wrapper. It works beautifully now!
 
Thanks for letting us know.
 
"There is an error in the query. Object reference not set to an instance of an object." This happens when trying to specify the query for the report. Any idea? Please.
 
It's ok.. I got it. Thanks for your article.
 
Hi,
I am trying to do the same thing described here but on SQL2005. I am wondering if the plug-in will work for SQL2005 or I should make some changes. Also, directory structure for reporting services in SQL 2005 is a little different. Is there resource on the web describing how to report off sharepoint with SQL 2005?

Eugene
 
i am getting http: unauthorised error 404

The credentials placed are perfect ..still i gt the error
 
Thanks for this service, it's very helpfull
yet when i want to have it run on another machine (with on designer), i get this error

Reporting Services Error
An error has occurred during report processing. (rsProcessingAborted)
Cannot create a data reader for data set 'Issues'. (rsErrorCreatingDataReader)
 
Apparently, in .NET 2.0, the attributes are case sensitive. You have to capitilize "Name" and "Type" for Reporting Services service to run properly.

<Extension Name="SPSLIST" Type="Macaw.ReportingServices.SharepointListExtension.Connection, Macaw.ReportingServices.SharepointListExtension"/>
 
Can I use these DLLs for RS2005?
 
@harshad: check out this post: http://www.teuntostring.net/blog/2006/04/using-sharepoint-lists-extension-on.html
 
Hi Tuen,

I have a serious problem to configure Reporting to use your dll. I do the following:

- I copy the dll in the directories that you said.

- I modify the file C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\RSReportServer.config and the file C:\Program Files\Microsoft SQL Server\MSSQL\Reporting Services\ReportServer\rssrvpolicy.config. What am i doing wrong?
 
I am getting the error while connecting to a WSS site.

Error: A connection cannot be made to the database. Set and check the connection string.

I configured the dlls in the respective config files and setup the policy too in rssrvpolicy.config. Can anybody help me.

Ganeshan
 
I am getting the error while connecting to a WSS site.

Error: A connection cannot be made to the database. Set and check the connection string.

I configured the dlls in the respective config files and setup the policy too in rssrvpolicy.config. Can anybody help me.

Ganeshan
 
In the Report Wizard --> Design the Query Window --> under Query String --> I try with the following and getting error

Query String: list=[WSS List Name]; view=[View Name]
Error: There is an error in the query. Object reference not set to an instance of an object.

Any help, much appreciated.
 
Did you:
* Set up the connect string correctly with the full URL of the team site you try to connect to?
* Configure username and password? In some configurations, integrated security does not work. Try first to hardcode passwords.
 
After specifying the query string I'm getting error:

There is an error in the query. Invalid URI: The format of the URI could not be determined.

could you pls help me.
 
Have you tried to use this with Sharepoint 2007?
 
@Jason: No, I haven't, but SPS2007 is supposed to be backward compatible with SPS2003, so I figure it would work. Not tested though.
 
This loks very good. I tried in doing in SQL2005 and I am not getting the SPLISTS in teh drop down. Can you please let me know what will be proces for doing with SQL 2005?
 
Wow !!
 
A simple solution that does exactly that... even free, CardioLog sharePoint Reporting solution

http://www.intlock.com

Uri
Intlock
 
While CardioLog seems a nice free solution, it does not do the same thing as the code provided here. CardioLog is a set of usage reports for SharePoint. The extension presented here allows you to use SharePoint lists as the datasource for Reporting Services reports. Quite different.
 
Post a Comment

Links to this post:

Create a Link



<< Home

This page is powered by Blogger. Isn't yours?