Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crystal Reports Viewer with MVC3

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?

like image 265
Tiago Cardoso Avatar asked Aug 06 '11 11:08

Tiago Cardoso


2 Answers

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();
      }
    }
  }
}
like image 23
Renan Marks Avatar answered Oct 10 '22 00:10

Renan Marks


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)

like image 114
Nick Daniels Avatar answered Oct 09 '22 22:10

Nick Daniels