Thursday, 23 May 2013

Using ReportViewer control in MVC 4 with Razor

The ReportViewer control for Browser apps is an ASP.NET webforms control.  There are quite a lot of posts on various forums about using this in MVC of varying levels of usefulness.  I wanted to see how to use it with an MVC 4 app in which I was using Razor as the view engine.  The starting point for the following was this post on stackoverflow, which pointed me in the right direction but it still required some faffing to get a working solution.  The following works for me.

The general approach is to create an ASP.NET webforms user control (reportViewerControl.ascx) and embed this in a Razor page using the @Html.RenderPartial helper.

To keep things tidy in my MVC project, I have created a folder called Reports under the project root, where I plan to keep my rdlc files and a folder under the Views folder, which I've also called Reports (i.e. Views\Reports) where I create the user control, which I plan to use to render the rdlc files.  I'm hoping with some cunning I may be able to use the control for all reports.

Step 1: Create an ASP.NET user control called ReportViewerControl.ascx in Views\Reports

ReportViewerControl.ascx looks like this

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ReportViewerControl.ascx.cs" Inherits="ReportViewerTest.Views.Reports.ReportViewerControl" %>

<%@ Register TagPrefix="rsweb" Namespace="Microsoft.Reporting.WebForms" Assembly="Microsoft.ReportViewer.WebForms, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<form id="form1" runat="server">

<div>
    <asp:ScriptManager ID="scriptManager" runat="server" EnablePartialRendering="false" />
   
    <asp:Panel ID="pnlReport" ClientIDMode="Static" runat="server">
    <rsweb:ReportViewer Width="100%" Height="100%" ZoomMode="PageWidth" SizeToReportContent ="True" ID="reportViewer" runat="server" >
                  <LocalReport>
                  </LocalReport>

    </rsweb:ReportViewer>
    </asp:Panel>
</div>
</form>

reportViewer is the ID of the control, referred to in the code behind lower down.

The script manager is needed to make things work

The control is embedded in a panel, to help fix some CSS issues I'll describe later.

Step 2: Design your report (rdlc file).  My test report is called PatientsList.rdlc.  I'm pulling my data from an entity model linked to a SQL Server database.  It looks like this


The dataset is called DataSet1 and I've created a parameter called Title to push in the report title (mainly as a way to demonstrate parameter passing)

Step 3: Add a reference (under references) to Microsoft.ReportViewer.Webforms.  You may see different versions (e.g. 9 or 10) under the .NET tab.  I used version 10.  Set the CopyLocal property to True, so that the dll gets included in the Publish operation.

Step 4: Back in the user control, make the code behind in ReportViewerControl.ascx.cs look like

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
//using System.Web.UI.WebControls;
namespace ReportViewerTest.Views.Reports
{
    //public partial class ReportViewerControl : System.Web.UI.UserControl   
    public partial class ReportViewerControl : System.Web.Mvc.ViewUserControl    
   {
        protected void Page_Load(object sender, EventArgs e)
        {

        }
        protected void Page_Init(object sender, EventArgs e)
        {
           // Required for report events to be handled properly.
            Context.Handler = Page;
            ShowReport();
        }

        protected void ShowReport()
        {
            My_DevEntities entities = new My_DevEntities();
            IQueryable<Patients> pdata = from p in entities.Patients
                                         select p;
            List<Patients> plist = pdata.ToList();
            //List<Patients> Model = plist;

            reportViewer.ProcessingMode = Microsoft.Reporting.WebForms.ProcessingMode.Local;
            reportViewer.LocalReport.ReportPath = "Reports\\PatientsList.rdlc";
            reportViewer.LocalReport.DataSources.Clear();
            reportViewer.LocalReport.DataSources.Add(new Microsoft.Reporting.WebForms.ReportDataSource("DataSet1", plist));


            //Set Parameters
            Microsoft.Reporting.WebForms.ReportParameter param = new Microsoft.Reporting.WebForms.ReportParameter("Title", "Patients Lost");
            reportViewer.LocalReport.SetParameters(param);

            reportViewer.LocalReport.Refresh();       
       
           }

    }
}
Some important points here:

a) The user control needs to inherit from System.Web.Mvc.ViewUserControl instead of
System.Web.UI.UserControl    
b) In Page_Init you need to set Context_Handler = Page; for the report events to work
c) Page_Init is also the place to refresh the report from.  If you do this stuff in Page_Load, the page keeps refreshing itself
d) I pass the data to the report as a List<>. 
e) I set the ProcessingMode to Local, the ReportPath to my rdlc file, clear out any data source defined in the rdlc and pass in DataSet1.  I can pass in multiple datasets, with repeated called to add new data sources to the local report.
f) I'm setting the Title parameter to "Patient List" but you can skip those two lines if you don't need to pass any parameters
g) Finally I refresh the local report

Step 5:  The Razor view

My Razor view is simply this

<h3>My Patient List</h3>
@{Html.RenderPartial("~/Views/Reports/ReportViewerControl.ascx");}

Some posts suggested using the @Html.Partial helper but when I did this, the page wouldn't load in the browser (IE10 or Chrome)

Navigating to this view gives


 which is a good start but the ReportViewer toolbar is a mess.  The problem is that MVC4 Internet projects out of the box, define some CSS for tables.  The following CSS, added to the Content\Site.css file and applied specifically to the panel, fixes this in Chrome and IE10 (haven't tested this for other browsers yet)

/*****************************
*   Extras for ReportViewer  *
******************************/
#pnlReport table, #pnlReport td, #pnlReport span, #pnlReport a, #pnlReport input
{
    padding: 0 !important;
    margin: 0 !important;
}
#pnlReport input
{
    background-color: Transparent;
}
#pnlReport input[type=text]
{
    background-color: #FFF;
    width:200px;
}
/*****************************
*   End of Extras            *
******************************/

The View now renders as


which is just what I wanted.  All of the ReportViewer toolbar functions work as expected.

One final note.  When you make changes to any of the code and rerun the app, you will probably need to do a  Ctrl F5 before you see the effects of the changes.  I mention it here because I always forget this!

No comments:

Post a Comment