We were able to get the report viewer to work and have been using it for the past few months in production without any issues.
- We have a reports controller that lists links to the reports we want to run
- Clicking on one of the links will make an ajax call to the back end and return a partial page where we can fill in all the parameters we need.
- After the parameters are filled out we submit the form to '\reports\Name of Report'.
- Back in the Reports controller we call SQL, return our data, and then call a different view called 'Full Report'
- The 'Full Report' View only has a crystal report viewer control on it where it automatically takes the report data we pass over to it through ViewData, populates the report, renders it, and sends it to the user
Everything seems to work great.
UPDATE
I've added some code and clarification to the steps I originally listed above. The key item I left out was there is some code behind with the final View so it will work with Crystal Reports. The code behind is minimal, but needed. For Crystal Reports to work you are going to end up with the following files:
- A layout file.rpt where you design the report
- A aspx file that holds the Crystal Reports Report control. This is the file that will have some code behind.
Details on how to create a View that will work with Crystal Reports:
- Create the layout of your report using the Crystal Reports Designer. The resulting file will be an .rpt file. For the sake of this example, let's call this file AllJobsSummaryReportLayout.rpt.
- While designing your report, for the 'Database Fields' select one of the business entities or DTOs that holds the results coming back from SQL.
- A quick aside, we have a few data transfer objects (DTOs) in our system that contain nothing more than scalar values and strings, there is no intelligence in these DTOs. When the Controller is called, it calls the Model, the Model for most of these reports returns a List of DTOs that we then pass to the View to be rendered. These DTOs do not know how to query themselves, display themselves, they only contain actual values returned from SQL that someone else then renders.
Once the layout Crystal Report file is completed, the AllJobsSummaryReportLayout.rpt, we design our Controller. In the Controller we take in any parameters needed to run the report, call the Model, Model returns our list of DTOs, as seen in the snippet below from the Controller:
var reportViewData = model.AllJobsSummaryQuery(startDate, endDate);
if (0 != reportViewData.Count())
{
var report = new AllJobsSummaryReportLayout();
report.SetDataSource(reportViewData);
report.SetParameterValue("startDate", startDate);
report.SetParameterValue("endDate", endDate);
ViewData["ReportData"] = report;
returnView = "AllJobsSummaryView";
}
else
returnView = "noReportView";
return View(returnView);
Note a couple of items here, we are creating a varible 'report' that is a type of the Crystal Report layout file, AllJobsSummaryReportLayout.rpt, that we created above.
Once we created the 'report' variable we set the data source values and any parameters we need, and bundle the item up into the ViewData.
Now let's take a look at AllJobsSummaryView.aspx. This file has a form on it with a Crystal Reports Viewer and a code behind file:
<%@ Page Title="All Jobs Summary Report" Language="C#" AutoEventWireup="true" CodeBehind="AllJobsSummaryView.aspx.cs" Inherits="V.Views.Reports.AllJobsSummaryView"%>
<%@ Register Assembly="CrystalDecisions.Web, Version=10.5.3700.0, Culture=neutral, PublicKeyToken=692fbea5521e1304" Namespace="CrystalDecisions.Web" TagPrefix="CR" %>
<form id="form1" runat="server">
<div>
<a href="/Reports" id="Report"><< Return to Report Main
Page</a><br />
<CR:CrystalReportViewer ID="ReportViewer" runat="server" AutoDataBind="True" EnableDatabaseLogonPrompt="False"
EnableParameterPrompt="False" HasCrystalLogo="False" DisplayGroupTree="False"
HasDrillUpButton="False" HasToggleGroupTreeButton="False" HasViewList="False"
HasSearchButton="False" EnableDrillDown="False" EnableViewState="True"
Height="50px" ReportSourceID="CrystalReportSource1" Width="350px" />
<CR:CrystalReportSource ID="CrystalReportSource1" runat="server">
<Report FileName="AllJobsSummaryReportLayout.rpt">
</Report>
</CR:CrystalReportSource>
</div>
</form>
And the code behind file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace V.Views.Reports
{
public partial class AllJobsSummaryView : ViewPage
{
protected void Page_Init(object sender, EventArgs e)
{
ReportViewer.ReportSource = ViewData["ReportData"];
}
protected void Page_Unload(object sender, EventArgs e)
{
((AllJobsSummaryReportLayout)ViewData["ReportData"]).Close();
((AllJobsSummaryReportLayout)ViewData["ReportData"]).Dispose();
}
}
}
The Page_Unload is key, without it you will have an error generated by Crystal Reports 'You have exceeded the max number of reports set by your administrator.'
This method is still working in a production environment for well over two years now.