It's rather amazing how this stuff keeps coming back to haunt me. The above reply which I originally marked as a solution, has in fact proven to not be a real solution after all. Reading back through Ludek's explanation of the different SDKs, I realize that my simplified viewer solution really only worked as long as the target data source never differed from the one used during the designing of the report.
Well, now that situation has occurred since we have migrated our web applications to a new data center. Sure, I could take the easy route and just update the data sources in all of my report files, but that's not a proper solution. So I've gone back to my code and tried to implement a solution using the ReportDocument object. What I've found is I can make it work for a report which has parameters and lets the viewer prompt for input, but on a report that has no parameters, I just get the same old "The report filename was empty" error. I've created a test page to isolate these examples and included only the essential code to demonstrate the different behavior.
using System; | |
using System.Configuration; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Web; | |
using System.Web.UI; | |
using System.Web.UI.WebControls; | |
using System.Data; | |
using System.Data.Common; | |
using System.Data.SqlClient; | |
using CrystalDecisions.Shared; | |
using CrystalDecisions.CrystalReports.Engine; | |
namespace FinancialReporting | |
{ | |
public partial class TestReport : System.Web.UI.Page | |
{ | |
protected void Page_Load(object sender, EventArgs e) | |
{ | |
if (Page.IsPostBack) | |
{ | |
// Postback triggered by parameter submission, print, or export request, | |
// so check to see if it's one of the dynamic DataSet-driven reports. | |
ReportDocument rptDoc = (ReportDocument)Session["DynamicReport"]; | |
if (rptDoc != null) | |
{ | |
DisplayReportInViewer(rptDoc); | |
} | |
} | |
else | |
{ | |
Session.Remove("DynamicReport"); | |
string filename = GetReportFileName(); | |
try | |
{ | |
if (filename != string.Empty) | |
ConfigureCrystalWithDbOverride(filename); | |
} | |
catch (Exception ex) | |
{ | |
} | |
} | |
} | |
private string GetReportFileName() | |
{ | |
string filename = string.Empty; | |
switch (Request.QueryString["rpt"]) | |
{ | |
// MAS Custom Reports | |
case "EmpUtilByWeek": | |
// This report contains parameters for which CR viewer will prompt | |
filename = "~/Reports/EmpUtilByWeek.rpt"; | |
break; | |
case "AccrualAging": | |
// This report contains no parameters | |
filename = "~/Reports/AccrualAging.rpt"; | |
break; | |
default: | |
break; | |
} | |
return filename; | |
} | |
// This code only works if you are not trying to override the data source used to design the report | |
private void ConfigureCrystalUsingDesignDb(string ReportFile) | |
{ | |
this.crviewer1.ReportSource = Server.MapPath(ReportFile); | |
// If the selected report has parameters, clear them out first to ensure the viewer prompts each | |
// time for user input. | |
if (this.crviewer1.ParameterFieldInfo.Count > 0) | |
this.crviewer1.ParameterFieldInfo.Clear(); | |
// Get database logon info from web.config | |
CrystalDecisions.Shared.TableLogOnInfo logon = GetReportLogonInfo(); | |
this.crviewer1.LogOnInfo.Add(logon); | |
} | |
private void ConfigureCrystalWithDbOverride(string ReportFile) | |
{ | |
ConnectionInfo crConn = CreateConnectionInfo(); | |
ReportDocument rptDoc = new ReportDocument(); | |
rptDoc.Load(Server.MapPath(ReportFile)); | |
Tables crTables = rptDoc.Database.Tables; | |
foreach (CrystalDecisions.CrystalReports.Engine.Table tbl in crTables) | |
{ | |
tbl.LogOnInfo.ConnectionInfo = crConn; | |
tbl.ApplyLogOnInfo(tbl.LogOnInfo); | |
} | |
// If the selected report has parameters, clear them out first to ensure the viewer prompts each | |
// time for user input. | |
if (this.crviewer1.ParameterFieldInfo.Count > 0) | |
this.crviewer1.ParameterFieldInfo.Clear(); | |
// Get database logon info from web.config | |
Session["DynamicReport"] = rptDoc; // Save to Session object which will be needed during subsequent postbacks | |
DisplayReportInViewer(rptDoc); | |
} | |
private CrystalDecisions.Shared.TableLogOnInfo GetReportLogonInfo() | |
{ | |
CrystalDecisions.Shared.TableLogOnInfo logon = new CrystalDecisions.Shared.TableLogOnInfo(); | |
CrystalDecisions.Shared.ConnectionInfo connInfo = new CrystalDecisions.Shared.ConnectionInfo(); | |
string connectionString = ConfigurationManager.ConnectionStrings["MAS_ConnectionString"].ConnectionString; | |
string[] conn = connectionString.Split(';'); | |
foreach (string s in conn) | |
{ | |
string[] keyVal = s.Split('='); | |
switch (keyVal[0].ToLower()) | |
{ | |
case "data source": | |
connInfo.ServerName = keyVal[1]; | |
break; | |
case "initial catalog": | |
connInfo.DatabaseName = keyVal[1]; | |
break; | |
case "user id": | |
connInfo.UserID = keyVal[1]; | |
break; | |
case "password": | |
connInfo.Password = keyVal[1]; | |
break; | |
} | |
} | |
logon.ConnectionInfo = connInfo; | |
return logon; | |
} | |
private ConnectionInfo CreateConnectionInfo() | |
{ | |
ConnectionInfo crInfo = new ConnectionInfo(); | |
string conn = ConfigurationManager.ConnectionStrings["MAS_ConnectionString"].ConnectionString; | |
SqlConnection sqlConn = new SqlConnection(conn); | |
crInfo.ServerName = sqlConn.DataSource; | |
crInfo.DatabaseName = sqlConn.Database; | |
crInfo.IntegratedSecurity = true; | |
return crInfo; | |
} | |
private void DisplayReportInViewer(ReportDocument rptDoc) | |
{ | |
this.crviewer1.ReportSource = rptDoc; | |
this.crviewer1.RefreshReport(); | |
} | |
} | |
} |
This page is called passing in a querystring value which refers to either one of the two reports in question. I believe the code comments I've added should make it fairly clear what I'm trying to do.