Recently one of my friend mentioned strange Crystal Reports behavior on ASP.net page. The scenario was simple,
- ASP.net web page
- A Button
- A CrystalReportViewer Control
- A simple Crystal Report
What he doing was, on clicking of button he was loading the report in CrsytalReportViewer control. The report was being displayed correctly, but when he was trying to Zoom In, Zoom Out or Exporting the report he was getting error “No valid report source is available”.
Here is the code he wrote in the Submit button click handler.
protected void btnSubmit_Click(object sender, EventArgs e) { CrystalDecisions.CrystalReports.Engine.ReportDocument rpt; rpt = new CrystalDecisions.CrystalReports.Engine.ReportDocument(); rpt.Load(Server.MapPath("TestReport.rpt")); this.CrystalReportViewer1.ReportSource = rpt; }
Protected Sub btnSubmit_Click(sender As Object, e As System.EventArgs) Handles btnSubmit.Click Dim rpt As CrystalDecisions.CrystalReports.Engine.ReportDocument rpt = New CrystalDecisions.CrystalReports.Engine.ReportDocument() rpt.Load(Server.MapPath("TestReport.rpt")) Me.CrystalReportViewer1.ReportSource = rpt End Sub
I don’t know the internal working CrystalReportViewer control, but it seems that it is NOT loading the ReportSource property back from ViewState properly. Here is the following that I think is happening internally,
- Page is POSTed back
- Page_Load event
- CrystalReportViewer controls’ Load event
- Submit button Click event: in event handler, we load the report which is then rendered perfectly
- CrystalReportViewer control POST back the page using JavaScript
- Page_Load event
- CrystalReportViewer control’s Load event: In event handler, it tries to access the ReportSource property without first loading it from ViewState, on this point it does NOT find any ReportSource that’s why it returns back this message.
Note that above is my hypothesis, I don’t know actually how it works. Anyway after this I thought if my hypothesis is correct then we need to load the report on Page_Load event. So that when CrystalReportViewer control’s load event occurs, it should have the ReportSource property properly defined.
So I change changed the flow as,
- I moved the Report Load code from Submit button click handler to Page_Load event handler
- But in a way that for the first time report does NOT get displayed
- Instead, when user hits Submit button, then I mark a flag in a hidden field (using JavaScripting)
- And then when Page_Load event handler executes, it checks that hidden field for the flag, if flag is there then it loads the report.
Finally it is a good practice to dispose of the Report object in order to avoid any memory issues. Here is the full code of page.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class TestForm : System.Web.UI.Page { CrystalDecisions.CrystalReports.Engine.ReportDocument rptDoc = null; protected void Page_Load(object sender, EventArgs e) { if (this.IsPostBack) { // we will only display report on post back if (this.txtShowReport.Value == "1") { this.rptDoc = new CrystalDecisions.CrystalReports.Engine.ReportDocument(); this.rptDoc.Load(Server.MapPath("TestReport.rpt")); this.CrystalReportViewer1.ReportSource = this.rptDoc; this.CrystalReportViewer1.DataBind(); } } } protected void Page_Unload(object sender, EventArgs e) { if (this.rptDoc != null) { this.rptDoc.Close(); this.rptDoc.Dispose(); } } }
Partial Class TestFormVB Inherits System.Web.UI.Page Private rptDoc As CrystalDecisions.CrystalReports.Engine.ReportDocument = Nothing Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load If Me.IsPostBack Then If Me.txtShowReport.Value = "1" Then Me.rptDoc = New CrystalDecisions.CrystalReports.Engine.ReportDocument() Me.rptDoc.Load(Server.MapPath("TestReport.rpt")) Me.CrystalReportViewer1.ReportSource = rptDoc End If End If End Sub Protected Sub Page_Unload(sender As Object, e As System.EventArgs) Handles Me.Unload If Me.rptDoc IsNot Nothing Then Me.rptDoc.Close() Me.rptDoc.Dispose() End If End Sub End Class
In case if anyone wondering how we can set value in a hidden field through JavaScript here is the code, its very simple. You need to add the following markup in the Head Section of your HTML
<script type="text/javascript"> function ShowReportFlag() { document.getElementById("<%:this.txtShowReport.ClientID %>").value = "1"; } </script>
And the then you need to add the exeuction of this method in your Button
<asp:Button ID="btnShowReport" runat="server" OnClientClick="javascript:ShowReportFlag();" text="Show Report" />
No comments:
Post a Comment