I am building an ASP.NET MVC3 web application and I want to build some reports using crystal reports and display them using crystal reports viewer. I searched through the web and i didn't find any solid way of doing it in an MVC3 web application. Do you have any hints/ideas of how to do it?
In our projects we are returning the report directly in PDF format. We chosed to not use both WebForms and MVC in the same project mainly because of keepping the codebase clean.
The reports are generated against a "dumb" dataset created manually and with data filled by a service class that retreive all the information needed through NHibernate (because we're using a layer of ORM persistence and DB abstraction, the Crystal Reports must not connect directly to the database).
If your project does not have the necessity of showing the report in the browser as a "preview mode", here is the custom ActionResult that I wrote for this kind of situation:
public class PdfReportResult : ViewResult
{
/// <summary>
/// Crystal Report filename
/// </summary>
public string reportFileName { get; set; }
/// <summary>
/// DataSet used in the report
/// </summary>
public DataSet reportDataSet { get; set; }
/// <summary>
/// Report parameters
/// </summary>
IDictionary<string, object> parameters { get; set; }
public PdfReportResult(string reportFileName, DataSet reportDataSet, IDictionary<string, object> parameters)
{
if (string.IsNullOrEmpty(reportFileName))
throw new ArgumentException("Report filename not informed", "reportFileName");
if (reportDataSet == null)
throw new ArgumentException("DataSet not informed", "reportDataSet");
if (parameters == null)
this.parameters = new Dictionary<string, object>();
else
this.parameters = parameters;
this.reportDataSet = reportDataSet;
this.reportFileName = reportFileName;
}
public PdfReportResult(string reportFileName, DataSet reportDataSet)
: this(reportFileName, reportDataSet, null)
{ }
public override void ExecuteResult(ControllerContext context)
{
if ( context == null )
throw new ArgumentNullException("context");
if ( string.IsNullOrEmpty(this.ViewName) )
this.ViewName = context.RouteData.GetRequiredString("action");
// Alias to make the code more legible
HttpResponseBase response = context.HttpContext.Response;
// Generating the report
using ( ReportDocument report = new ReportDocument() )
{
// Load the report
report.Load(context.HttpContext.Server.MapPath("~/Reports/" + reportFileName));
// Set the dataset
report.SetDataSource(reportDataSet);
// Set the parameters (if any)
foreach (var parameter in parameters)
report.SetParameterValue(parameter.Key, parameter.Value);
// Send back the PDF to the user
using ( MemoryStream oStream = (MemoryStream)report.ExportToStream(ExportFormatType.PortableDocFormat) )
{
response.Clear();
response.Buffer = true;
response.AddHeader("Content-Disposition", this.ViewName + ".pdf");
response.ContentType = "application/pdf";
response.BinaryWrite(oStream.ToArray());
response.End();
}
}
}
}
If you don't mind some hacking it's actually pretty easy. (Assuming CR4VS2010)
First add a WebForms page to your project and add the crystal reports viewer control to it.
Verify it added references to:
CrystalDescisions.CrystalReports.Engine
,
CrystalDescisions.ReportSource
,
CrystalDescisions.Shared
,
and CrystalDescisions.Web
.
Then add a PageRoute to your application leading to the newly added page.
Finally, and this was the biggest pain the BLANK, you'll need to make Crystal's Image Handler work. There are many supposed ways, both around the net and here at SO, none of them really worked for me so I resorted to cheating:
public class CrystalImageHandlerController : Controller
{
//
// GET: /CrystalImageHandler.aspx
public ActionResult Index()
{
return Content("");
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
var handler = new CrystalDecisions.Web.CrystalImageHandler();
var app = (HttpApplication)filterContext.RequestContext.HttpContext.GetService(typeof(HttpApplication));
if (app == null) return;
handler.ProcessRequest(app.Context);
}
}
Add a route to this controller as /CrystalReportsImageHandler.aspx, this is where CR expects it handler to be. This can also be used in Areas, just change the handler and page routes as needed.
Bear in mind you will not be able to use your Razor layouts. So you'll need to resort to other means to get visual continuity. (I used IFrames)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With