Monday, July 03, 2006

 

Integrating SQL Server 2005 Reporting Services into an ASP.Net application

I recently had a chance to work with SQL Server 2005 Reporting services. I must say I'm fairly impressed with the offering. You can pull data from just about anywhere into a single place and then serve up reports in almost any format you want. Data from office docs (xl, word), XML files, just about any data base, flat files can be mushed together and served up in HTML, back to xl, PDF, and/or email formats.

I am integrating reports in an ASP.NET 2.0 application. The reports appear as another page inside the app with all of our menus, navigation, security and such wrapped around it. VS2005 includes a new data control called Report Viewer. Just set some properties and it serves up your report.

My only problem I had was in the deployment. Of course, everything works fine during development while everything is running on my local machine. The problem comes into play when the production environment is used where reporting services runs on a different machine from the web server. I was continually getting "The request failed with HTTP status 401" failure. Nothing in the IIS or Reporting Services log was helpful.

I eventually stumbled on a property of ServerReport called ReportServerCredentials. This was the secret element that eventually solved my problem. I found a sample on MSDN that got me most of the way there and it reminded me of this joke (scroll down to lost in space). It seems you can create a custom object to implement the IReportServerCredentials interface. The sample shows using forms authentication and a cookie. It turns out I what I wanted to use was the network credentials option instead. Using the debugger and trial and error, it seems that when have your custom object, reporting services first tries to use WindowsIdentity, then NetworkCredentials then GetFormsCredentials. WindowsIdentity returns a null value which appears to be a signal to try something else. GetFormsCredentials needs to return false after setting all of the out parameters to null. NetworkCredentials actually returns a System.Net.ICredentials object. I just created some properties in my web.config for ID and password.

On the report server machine, I added a local user and removed all logon rights. It's not the best security because the ID and password are in plain text in the file but there are other ways to work around that. So after 6 hours of research, I now have a fully functional report viewer integrated into my application.

Problem #2 was that I was getting my report rendered (i.e. I could see the little animation showing "generating report") but upon render, it never showed up in the HTML for the page. I did a quick web search and found some entries showing that if you set width and height to 100% that could cause the problem. I tried setting both to actual values (e.g. 400px). However, this caused scroll bars when the reports were larger than this.

A better fix seemed to be to change width and height back to 100% and set the viewer property AsyncRendering to false. Now the page takes a few extra seconds to display while the report is being retrieved but it correctly shows in the output.

All in all, I'd say I'm very happy with reporting services. Just a few little tweaks and maybe some better documentation would make it a fine tool.



Tweaked Sample implementation of IReportServerCredentials

///
/// custom objec to supply credentials for report server.
/// this implementation asks the utility for the ID and password
/// and relies on NetworkCredentials to login to the report server.
/// the ID configured must exist on the report server and be granted
/// access to execute the specified reports.
///
/// The ID should be denied "logon locally" access to prevent
/// misuse.
///
///

private class MyReportServerCredentials : IReportServerCredentials
{

public MyReportServerCredentials()
{
}

public WindowsIdentity ImpersonationUser
{
get
{
return null;
}
}

public System.Net.ICredentials NetworkCredentials
{
get
{
NetworkCredential nc = new NetworkCredential();
nc.UserName = ReportUtilities.ReportUserID;
nc.Password = ReportUtilities.ReportPassword;
return nc; // Using NetworkCredentials to authenticate.
}
}

public bool GetFormsCredentials(out System.Net.Cookie authCookie, out string user, out string password, out string authority)
{
authCookie = null;
user = password = authority = null;
return false; // do not use forms credentials to authenticate.
}
}


Sample call from ASP.NET page. Call from Page_Load() or other UI event method. This is a business layer routine that causes the report to be rendered in the output. Security checks are omitted from this method.

public static void Render(System.Web.UI.Page report, ReportViewer viewer)
{
string reportID = ID(report); // lookup path from query string

// perform security validation here

viewer.ServerReport.ReportServerUrl = ReportServerUrl; // property from web.config
viewer.ServerReport.ReportPath = ReportPath(reportID); // util function
viewer.ServerReport.ReportServerCredentials = new MyReportServerCredentials();
}

Call from ASP.Net code behind
protected void Page_Load(object sender, EventArgs e)
{
// Security Access check is performed by Render utility.
ReportUtilities.Render(this, this.Report);
}

Comments: Post a Comment

<< Home

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