Wednesday, July 25, 2012

RDLC Reports in MVC Web application


Step by Step adding an RDLC report to MVC4 project

For any large project, reports will be needed. I love RDLC so I’m sticking with them. I had some problems in the beginning but with some search here and there and some testing it start working. So I will describe it from the beginning.
Requirements:
·         In my new MVC I need to have a number of RDLC reports
·         I need the reports to read from objects, direct entities or report objects (for large reports I prefer to something like a Report View Model, in the end a report is view for data)
·         A report my require one or more data sets
·         Some of the reports will need parameters that the user will select from the MVC pages
·         I want to export the report by  PDF, but having word and Excel would be an advantage
·         Some report standardization point that is needed:
o   Required reports have a description title
o   Required have right and left main and sub headers
o   Required to have the user name printing the report in the footer
o   Required to have the printing date on the footer
o   Required to have number of page and total pages in the footer
o   All the reports are A4 stander reports ready for printing.
o   Reports will be multi language (currently English and Arabic)
o   Reports may contain charts and bars
o   Reports need to be secure
·         The system contains an alert module and some of the requirement needs the PDF document to be attached to the email.

So lets start clean and from the beginning.
Lunching Visual Studio, selecting new project
Selecting MVC4, intrenet application (all template will work too)

Adding a new project with the name: ASPNET_MVC_RDLC

I will start putting it all in one project; it’s the same if you have business and data access layers that are in separate projects too, only to make it short im putting it all together and skipping testing too.



 So let’s start by adding the report view model that will handle the report parameters, headers and data sets




   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:  using Microsoft.Reporting.WebForms;
   6:  using System.Web;
   7:  using System.Web.Mvc;
   8:   
   9:  namespace ASPNET_MVC_RDLC.Models
  10:  {
  11:      public class ReportViewModel
  12:      {
  13:          public enum ReportFormat         { PDF=1,Word=2,Excel=3}
  14:          public ReportViewModel()
  15:          {
  16:              //initation for the data set holder
  17:              ReportDataSets = new List<ReportDataSet>();
  18:          }
  19:   
  20:          //Name of the report
  21:          public string Name { get; set; }
  22:   
  23:          //Language of the report
  24:          public string ReportLanguage { get; set; }
  25:   
  26:          //Reference to the RDLC file that contain the report definition
  27:          public string FileName { get; set; }
  28:   
  29:          //The main title for the reprt
  30:          public string ReportTitle { get; set; }
  31:   
  32:          //The right and left titles and sub title for the report
  33:          public string RightMainTitle { get; set; }
  34:          public string RightSubTitle { get; set; }
  35:          public string LeftMainTitle { get; set; }
  36:          public string LeftSubTitle { get; set; }
  37:   
  38:          //the url for the logo, 
  39:          public string ReportLogo { get; set; }
  40:   
  41:          //date for printing the report
  42:          public DateTime ReportDate { get; set; }
  43:   
  44:          //the user name that is printing the report
  45:          public string UserNamPrinting { get; set; }
  46:   
  47:          //dataset holder
  48:          public List<ReportDataSet> ReportDataSets { get; set; }
  49:     
  50:          //report format needed
  51:          public ReportFormat Format { get; set; }
  52:          public bool ViewAsAttachment { get; set; }
  53:   
  54:           //an helper class to store the data for each report data set
  55:          public class ReportDataSet
  56:          {
  57:              public string DatasetName { get; set; }
  58:              public List<object> DataSetData { get; set; }
  59:          }
  60:   
  61:          public string ReporExportFileName { get {
  62:              return string.Format("attachment; filename={0}.{1}", this.ReportTitle, ReporExportExtention);
  63:          } }
  64:          public string ReporExportExtention
  65:          {
  66:              get
  67:              {
  68:                  switch (this.Format)
  69:                  {
  70:                      case ReportViewModel.ReportFormat.Word: return  ".doc"; 
  71:                      case ReportViewModel.ReportFormat.Excel: return ".xls"; 
  72:                      default:
  73:                          return ".pdf";
  74:                  }
  75:              }
  76:          }
  77:   
  78:          public string LastmimeType
  79:          {
  80:              get
  81:              {
  82:                  return mimeType;
  83:              }
  84:          }
  85:          private string mimeType;
  86:          public byte[] RenderReport()
  87:          {
  88:              //geting repot data from the business object
  89:   
  90:              //creating a new report and setting its path
  91:              LocalReport localReport = new LocalReport();
  92:              localReport.ReportPath =System.Web.HttpContext.Current.Server.MapPath(this.FileName);
  93:   
  94:              //adding the reort datasets with there names
  95:              foreach (var dataset in this.ReportDataSets)
  96:              {
  97:                  ReportDataSource reportDataSource = new ReportDataSource(dataset.DatasetName, dataset.DataSetData);
  98:                  localReport.DataSources.Add(reportDataSource);
  99:              }
 100:              //enabeling external images
 101:              localReport.EnableExternalImages = true;
 102:   
 103:              //seting the partameters for the report
 104:              localReport.SetParameters(new ReportParameter("RightMainTitle", this.RightMainTitle));
 105:              localReport.SetParameters(new ReportParameter("RightSubTitle", this.RightSubTitle));
 106:              localReport.SetParameters(new ReportParameter("LeftMainTitle", this.LeftMainTitle));
 107:              localReport.SetParameters(new ReportParameter("LeftSubTitle", this.LeftSubTitle));
 108:              localReport.SetParameters(new ReportParameter("ReportTitle", this.ReportTitle));
 109:              localReport.SetParameters(new ReportParameter("ReportLogo", System.Web.HttpContext.Current.Server.MapPath(this.ReportLogo)));
 110:              localReport.SetParameters(new ReportParameter("ReportDate", this.ReportDate.ToShortDateString()));
 111:              localReport.SetParameters(new ReportParameter("UserNamPrinting", this.UserNamPrinting));
 112:   
 113:              //preparing to render the report
 114:   
 115:              string reportType = this.Format.ToString();
 116:             
 117:              string encoding;
 118:              string fileNameExtension;
 119:   
 120:              //The DeviceInfo settings should be changed based on the reportType
 121:              //http://msdn2.microsoft.com/en-us/library/ms155397.aspx
 122:              string deviceInfo =
 123:              "<DeviceInfo>" +
 124:              "  <OutputFormat>" + this.Format.ToString() + "</OutputFormat>" +
 125:              "</DeviceInfo>";
 126:   
 127:              Warning[] warnings;
 128:              string[] streams;
 129:              byte[] renderedBytes;
 130:   
 131:              //Render the report
 132:              renderedBytes = localReport.Render(
 133:                  reportType,
 134:                  deviceInfo,
 135:                  out mimeType,
 136:                  out encoding,
 137:                  out fileNameExtension,
 138:                  out streams,
 139:                  out warnings);
 140:   
 141:              return renderedBytes;
 142:          }
 143:      }
 144:      
 145:  }



lets add a new report called MyMVCReport.rdlc as below:
we will need to add some stub methods to set the report data sources

Set the data source for the report using the stub methods


   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:   
   6:  namespace ASPNET_MVC_RDLC.Models
   7:  {
   8:      public class Business
   9:      {
  10:          public List<Employee> StubForEmployeeDataSet()
  11:          {
  12:              //this is used only to help in adding the dataset of type employee to the report definition
  13:              return null;
  14:          }
  15:          public List<Project> StubForProjectDataSet()
  16:          {
  17:              //this is used only to help in adding the dataset of type project to the report definition
  18:              return null;
  19:          }
Note: in production code, I don't recommend putting this in the business layer, any report stub class will be better.


Set the data source for the report using the stub methods
so we have a problem here when selecting add data set from the report menu, the data source drop down list is empty, and when you click new it open a connection to the database.
So after some search I found a solution from stackoverflow, simply add a web form page to the MVC project and it will start working, I didn't have time to check why is this, but it works.
lets add another one.




lets set the design and layout of the report
the above is only for testing.

coming near the last steps , we need to add a reference to the Microsoft web form reports assembly

now lets add the controller that will produce the report.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Web;
   5:  using System.Web.Mvc;
   6:  using ASPNET_MVC_RDLC.Models;
   7:   
   8:  namespace ASPNET_MVC_RDLC.Controllers
   9:  {
  10:      public class ReportController : Controller
  11:      {
  12:          public ActionResult PrintRepert()
  13:          {
  14:              //geting repot data from the business object
  15:              var Business = new Business();
  16:              var reportViewModel = Business.GetMyRepoertViewModel();
  17:   
  18:              var renderedBytes = reportViewModel.RenderReport();
  19:   
  20:              if(reportViewModel.ViewAsAttachment)
  21:                  Response.AddHeader("content-disposition", reportViewModel.ReporExportFileName);
  22:              return File(renderedBytes, reportViewModel.LastmimeType);
  23:   
  24:          }
  25:   
  26:      }
  27:  }

Add a link to the page to open the report wherever is needed.


@{
    ViewBag.Title = "Home Page";
}
@section featured {
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1>@ViewBag.Title.h1>
                <h2>@ViewBag.Messageh2>
            hgroup>
          
        div>
    section>
}
<h3>We suggest the following:h3>
<ol class="round">
    <li class="one">
        <h5>Print an RDLC report in MVCh5>
          @Html.ActionLink("Print Report", "PrintRepert","Report")
    li>
 
ol>

ok lets run and see.

And its working, there still more work to be done as checking the other formats, the email attachment, 
adding some security, the Multilanguage, and uploading this example and linking it here.

I would love to here some comments.

Update: for the business class, it really must come from your original entities. so for this sample this is the one im using
   1:   
   2:  namespace ASPNET_MVC_RDLC.Models
   3:  {
   4:      public class Business
   5:      {
   6:          public List<Employee> StubForEmployeeDataSet()
   7:          {
   8:              //this is used only to help in adding the dataset of type employee to the report definition
   9:              return null;
  10:          }
  11:          public List<Project> StubForProjectDataSet()
  12:          {
  13:              //this is used only to help in adding the dataset of type project to the report definition
  14:              return null;
  15:          }
  16:          //somew
  17:          public ReportViewModel GetMyRepoertViewModel()
  18:          { 
  19:          // I will not go through getting data from ef, i will assume some test data here
  20:   
  21:            //first dataset, employee info
  22:              var EmplyeeDataSet =new List<object>() {
  23:                      new Employee() { Name="Ahmed Khaled", Mobile = "055111111", Email="Ahmed.Khaled@Somewhere.com", BirthDate=new DateTime(1970,10,10)},
  24:                      new Employee() { Name="Jamal Awsom" , Mobile = "055222222", Email="Jamal.Awsom@Somewhere.com", BirthDate=new DateTime(1977,1,10)},
  25:                      new Employee() { Name="James Salem" , Mobile = "055333333", Email="James.Salem@Somewhere.com", BirthDate=new DateTime(1982,12,10)},
  26:                      new Employee() { Name="Tony Read"   , Mobile = "055444444", Email="Tony.Read@Somewhere.com", BirthDate=new DateTime(1982,4,10)}
  27:        
  28:          };
  29:          var ProjectDataSet =new List<object>() ;
  30:              for (int i=1;i<30;i++)
  31:              {
  32:                  EmplyeeDataSet.Add(new Employee() { Name = "James Salem", Mobile = "055333333", 
  33:                      Email = "James.Salem@Somewhere.com", BirthDate = new DateTime(1982, 12, 10) });
  34:              }
  35:              
  36:              //Assuming the person printing the report is me
  37:              var UserPrinting="Ali Taki";
  38:   
  39:              var reportViewModel = new ReportViewModel()
  40:              {
  41:                  FileName = "~/Reprots/MyMVCReport2.rdlc",
  42:                  LeftMainTitle = "ABC Company Name",
  43:                  LeftSubTitle = "DEF Department Name",
  44:                  RightMainTitle = "اسم الشركة",
  45:                  RightSubTitle = "اسم القسم",
  46:                  Name = "Statistical Report",
  47:                  ReportDate = DateTime.Now,
  48:                  ReportLogo = "~/Content/logo.jpg",
  49:                  ReportTitle = "Summary report for top employee's and projects",
  50:                  ReportLanguage = "en-US",
  51:                  UserNamPrinting = UserPrinting,
  52:                  Format=ReportViewModel.ReportFormat.PDF,
  53:                  ViewAsAttachment=false,
  54:   
  55:              };
  56:              //adding the dataset information to the report view model object
  57:              reportViewModel.ReportDataSets.Add(new ReportViewModel.ReportDataSet() { DataSetData = EmplyeeDataSet.ToList(), DatasetName = "Employees" });
  58:              reportViewModel.ReportDataSets.Add(new ReportViewModel.ReportDataSet() { DataSetData = ProjectDataSet.ToList(), DatasetName = "Projects" });
  59:   
  60:   
  61:              return reportViewModel;
  62:          
  63:          }
  64:      }
  65:  }

32 comments:

  1. Can you elaborate on this:
    Business.GetMyRepoertViewModel();

    You didnt post this method

    Thanks

    ReplyDelete
  2. can you post this method:
    Business.GetMyRepoertViewModel();

    ReplyDelete
  3. can you post the code for:

    var reportViewModel = Business.GetMyRepoertViewModel();

    Or show all of the business class including the getmyreportviewmodel method.

    Thanks

    ReplyDelete
  4. Anonymous8:27 am

    Nice post,it was very simple to follow, could´nt see the result because of the missing part of the business class, if u can please post it!!


    Thanks

    ReplyDelete
  5. I updated the post to add the business class, I will update a full solution file for download soon for my examples, very sorry for the delay.

    ReplyDelete
    Replies
    1. Do you have a full solution file for download now?

      Thanks!

      Delete
    2. Do you have a full solution file for download now?

      Thanks!

      Delete
    3. Where is the solution for download ?

      TIA.

      Delete
  6. Is there a way to not have the report take up the whole browser screen? I'd still like my header and menu to still display and the report to simply replace the boyd of my _Layout.cshtml.

    Excellent post BTW. Works as advertised...

    ReplyDelete
  7. Hello, I have followed the tutorial, but I have an error on the part of renderdBytes, the error reads:
    "An error occurred locally During report processing"

    These lines at:

    localReport.Render renderedBytes = (

                    reportType,

                    deviceInfo,

                    out mimeType,

                    out encoding,

                    fileNameExtension out,

                    out streams,

                    out warnings);

    and not due to that, if I would have thought Algua helpful.

    regards

    ReplyDelete
    Replies
    1. hi, this mean that there is a problem with the report itself, most common issues can include:
      -- not providing the parameters (or parameter names are not correct)
      -- no providing the correct datasets, missing dataset or incorrect names
      hope this help.

      Delete
  8. Anonymous1:22 am

    Hello,
    Thanks for your post.
    I have a question.What does it mean stub method?
    why and when we can use this?

    ReplyDelete
  9. Thanks

    I have a question, is possible to get value of navigation properties in the report (rdlc)?

    ReplyDelete
    Replies
    1. yes get them by the MVC code and send them as parameter to the report

      Delete
  10. Anonymous1:48 am

    After adding Web.Form why Data Source is available. Please let me know.

    ReplyDelete
  11. Hello, Thanks, very helpful for me as a newbie.

    But i have problem getting data from ef, can you post sample about that

    ReplyDelete
  12. Anonymous1:50 pm

    It would be kind of you if you can give a sample solution to download :)
    Indeed, this subject reamains hot and very actual more than a year after...

    ReplyDelete
  13. Anonymous12:27 am

    could you please post your complete source file...waiting for ur reply

    ReplyDelete
  14. Found this very useful today - thanks for the post.

    ReplyDelete
  15. Anonymous12:41 am

    hi very very good
    please sample download

    ReplyDelete
  16. Excelent post, you saved me

    ReplyDelete
  17. Can we use ReportViewer control in MVC Application ?

    ReplyDelete
    Replies
    1. Yes, if you read the title "RDLC Reports in MVC Web application", This was for ASP MVC applications

      Delete
  18. Excelent Post Ali Taki, thank you so much for this tutorial it really helped me a lot!

    ReplyDelete
  19. Anonymous5:22 am

    Great stuff, this has really helped me, thank you.

    ReplyDelete
  20. Anonymous5:46 pm

    When I deploy to server not work, de dlls are correct in the bin folder. I need to install something in the server?

    ReplyDelete
  21. Anonymous8:31 am

    This article helped me a lot. I was having trouble getting to display the values sent through view models in my report and spend the whole weekend with no result. It took me some time to understand this article fully as it is goes back and forth anyhow It is a great article and Thank you for publishing this.

    ReplyDelete
  22. Anonymous4:17 pm

    Having problems (same error icon at the logo) when I'm trying to see the logo.
    RDLC - > external image -> [@Logo]
    I enable external images -> viewer.LocalReport.EnableExternalImages = true;
    Set the parameter with Server.MapPath(logopath);
    Render the report into pdf/xls

    Anyone knows why?

    ReplyDelete
  23. Anonymous2:51 pm

    Has anyone had any problems publishing a MVC app with rdlc reports to a production iis server? I have been able to get the reports working well but when I deploy the application to another server, first the ReportViewer dlls don't copy to the server in spite of setting copy local, and when I manually copy those files to the server I get the error:

    Could not load file or assembly 'Microsoft.ReportViewer.ProcessingObjectModel, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.

    Looking at the event viewer on the server the error reads:

    Could not load file or assembly 'Microsoft.ReportingServices.ProcessingObjectModel' or one of its dependencies. An attempt was made to load a program with an incorrect format.

    This looks like it might be a version mismatch.

    Any thoughts from others about this?

    ReplyDelete
    Replies
    1. will you will need to download and install the Microsoft report viewer redistributable (with the version that you are using) this will be good if you have a VPS and have a lot of web apps, or get the main DLL files and add them to your bin if you have one web site with reports or on a shared hosting plan.

      Delete