12/30/2024 Admin

Develop and Display RDLC Reports in Microsoft Blazor


image

You can design and display RDLC reports, developed using Visual Studio, in Microsoft Blazor.

This allows you to provide pixel perfect, printable reports, entirely in your Blazor application, without the need for an external report server.

This is made possible primarily by the functionally provided by these two projects:

The code covered in this article is available here: https://github.com/ADefWebserver/BlazorRDLCViewer.

 

The Sample Application

image

When you download the code from: https://github.com/ADefWebserver/BlazorRDLCViewer and run it in Visual Studio, on the Home page, you will see a screen that allows you to set a Report Title and enter inventory items.

When you have entered all your values, you can click the Generate Report button.

 

image

This will pass the values to the RDLC report and display the report on the page in a JavaScript viewer using code from PdfJs.

You will have the option to zoom in and out, and if there are multiple pages, navigate those pages using the previous and next buttons.

Finally, you will be able to initiate printing of the report using the print button.

 

image

The print dialog will show and allow you to select a printer.

You can then click the print button to complete the process.

 

The Report Viewer

image

The functionality of the report viewer, that displays the PDF created by Report Viewer Core, is provided by the PdfJs library.

It’s JavaScript code is referenced in the App.Razor page.

Custom code, written for this project, is contained in the wwwroot/js/site.js file.

That file is also referenced in the App.Razor page.

Finally, JavaScript methods to call the code in site.js is contained on the App.Razor page (noted in green in the image above).

 

image

The Home.razor page contains this markup to first display the controls to collect data, and the toolbar and the div to display the report when the Generate Report button is clicked.

Note the div to display the report uses the code:

@ref="pdfContainer"
so that it can be passed by reference to the JavaScript code so that it can place the rendered PDF in that location.

 

<PageTitle>Home</PageTitle>
@if (isReportGenerated)
{
    <div>
        Page: <span>@CurrentPage</span> / <span id="page_count">0</span> &nbsp;&nbsp;
        <button @onclick="PreviousPage">Previous</button>
        <button @onclick="NextPage">Next</button>
        <button @onclick="ZoomIn">Zoom In</button>
        <button @onclick="ZoomOut">Zoom Out</button>
        <button @onclick="PrintPdf">Print</button>
        <div @ref="pdfContainer" id="pdf-container" 
        style="border: 0px solid #ccc; width: 600px; height: 800px;"></div>
    </div>
}
else
{
    <!-- Display text box to obtain report title bound to ReportTitle -->
    <label for="reportTitle">Report Title:</label>
    <input type="text" id="reportTitle" @bind="ReportTitle" />
    <br /><br />
    <Datasheet Sheet="sheet"/>
    <br />
    <br />
    <button @onclick="PrepareReportAsync">Generate Report</button>
}

 

When the Generate Report button is clicked, the following code runs to open the RDLC report, pass the data to it, render it as a PDF, and pass it to the JavaScript viewer:

 

  private async Task PrepareReportAsync()
  {
      // Set the flag to indicate the report is generated
      isReportGenerated = true;
      // Create the report
      using var report = new LocalReport();
      // Define the report parameters
      var parameters = new[] { new ReportParameter("Title", ReportTitle) };
      // Load the report definition
      var reportPath = System.IO.Path.Combine("Reports", "Report.rdlc");
      if (!System.IO.File.Exists(reportPath))
      {
          throw new FileNotFoundException("Report file not found.", reportPath);
      }
      // Load the report definition (the RDLC file)
      await using var reportStream = System.IO.File.OpenRead(reportPath);
      using var reportReader = new System.IO.StreamReader(reportStream);
      report.LoadReportDefinition(reportReader);
      // Get all the values from the sheet
      var items = new List<ReportItem>();
      // Iterate through the rows in the sheet
      for (int i = 0; i < sheet?.NumRows; i++)
      {
          // Create a new report item
          var item = new ReportItem
              {
                  Description = sheet.Cells[i, 0].Value?.ToString() ?? string.Empty,
                  Price = 
                  decimal.TryParse(
                      sheet.Cells[i, 1].Value?.ToString(), out var price) ? price : 0m,
                  Qty = int.TryParse(
                      sheet.Cells[i, 2].Value?.ToString(), out var qty) ? qty : 0
              };
          // Only add items with a description
          if (!string.IsNullOrWhiteSpace(item.Description))
          {
              // Add the item to the list
              items.Add(item);
          }
      }
      // Add the data source to the report
      report.DataSources.Add(new ReportDataSource("Items", items));
      report.SetParameters(parameters);
      // Render the report to a PDF byte array
      byte[] pdfBytes = report.Render("PDF");
      // To display properly we need to save the PDF as a file and re-load it
      var pdfPath = System.IO.Path.Combine("Reports", 
      $"{DateTime.Now.Ticks.ToString()}-TempReport.pdf");
     
      // Save the pdf to the temp file
      System.IO.File.WriteAllBytes(pdfPath, pdfBytes);
      // Open the temp PDF file and read the bytes
      await using (var pdfStream = System.IO.File.OpenRead(pdfPath))
      {
          using (var memoryStream = new System.IO.MemoryStream())
          {
              await pdfStream.CopyToAsync(memoryStream);
              pdfBytes = memoryStream.ToArray();
          }
      }
      // Convert the PDF bytes to a base64 string
      var pdfBase64 = Convert.ToBase64String(pdfBytes);
      // Call the JavaScript function with the element reference and the PDF data
      await jsRuntime.InvokeVoidAsync("blazorPdfViewer.renderPdf", pdfContainer, pdfBase64);
      // Delete the temp pdf file
      System.IO.File.Delete(pdfPath);
  }

 

 

Designing Reports

image

To design reports you need to use Visual Studio 2022 (or higher) and you must install this extension: Microsoft RDLC Report Designer 2022.

 

image

You can make a new .rdlc file using the following code:

 

<?xml version="1.0" encoding="utf-8"?>
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition" xmlns:rd="http://schemas.microsoft.com/SQLServer/reporting/reportdesigner">
  <AutoRefresh>0</AutoRefresh>
  <ReportSections>
    <ReportSection>
      <Body>
        <Height>2in</Height>
        <Style />
      </Body>
      <Width>6.5in</Width>
      <Page>
        <LeftMargin>0.7874in</LeftMargin>
        <RightMargin>0.7874in</RightMargin>
        <TopMargin>0.7874in</TopMargin>
        <BottomMargin>0.7874in</BottomMargin>
        <ColumnSpacing>0.05118in</ColumnSpacing>
        <Style />
      </Page>
    </ReportSection>
  </ReportSections>
  <ReportParametersLayout>
    <GridLayoutDefinition>
      <NumberOfColumns>4</NumberOfColumns>
      <NumberOfRows>2</NumberOfRows>
    </GridLayoutDefinition>
  </ReportParametersLayout>
  <rd:ReportUnitType>Inch</rd:ReportUnitType>
  <rd:ReportID>156b21d2-9542-4902-99ef-7ec823774bb2</rd:ReportID>
</Report>

 

image

However, when you open it in the designer, and display the Report Data pane (go to the View menu in the top toolbar.Select Report Data from the dropdown)…

 

image

Initially you won’t have any object data sources available.

To create one, create a class like this, that represents the format of the data you will have on your report:

 

namespace BlazorRDLCViewer
{
    public class ReportItem
    {
        public string? Description { get; set; }
        public decimal Price { get; set; }
        public int Qty { get; set; }
        public decimal Total => Price * Qty;
    }
}

 

Reference that class by placing it in the types collection in the CreateSchemas method in the Schemas.razor file:

 

    private void CreateSchemas()
    {
        var types = new[] { typeof(ReportItem) };
        var xri = new System.Xml.Serialization.XmlReflectionImporter();
        var xss = new System.Xml.Serialization.XmlSchemas();
        var xse = new System.Xml.Serialization.XmlSchemaExporter(xss);
        foreach (var type in types)
        {
            var xtm = xri.ImportTypeMapping(type);
            xse.ExportTypeMapping(xtm);
        }
        using var sw = new System.IO.StreamWriter("Reports/ReportItemSchemas.xsd", false, Encoding.UTF8);
        for (int i = 0; i < xss.Count; i++)
        {
            var xs = xss[i];
            xs.Id = "ReportItemSchemas";
            xs.Write(sw);
        }
        status = "ReportItemSchemas.xsd created.";
    }

 

 

image

Run the application, select the Create Schemas page, and click the Create ReportItemSchemas.xsd button.

 

image

Stop the application and note that the ReportItemSchemas.xsd has been created.

 

image

Now a Data source and dataset can be selected.

 

image

Reports can be designed.

 

Notes

 

 

Download

The project is available at the following location: https://github.com/ADefWebserver/BlazorRDLCViewer

You must have Visual Studio 2022 (or higher) installed to run the code.

 

Links

An unhandled error has occurred. Reload 🗙