Monday, May 14, 2018

SSRS Reports in an Angular App (Part 2 - Showing the Report (correctly))

I recently wrote the first part of this series on displaying SSRS reports in an Angular application. At the end of that post I had a report, in HTML format, bound to an iframe and properly sized to display the full report without scrolling inside the iframe. However, I had a problem that some of the reports looked bad. Let's pick up from there.

I did some digging into the markup provided by SSRS and found that there's a bunch of jQuery that's used to do simple things like resize areas after they're rendered. When I bound that markup to the iframe, the jQuery wasn't able to run. That's actually the intended function. Since I was writing the markup into the iframe as opposed to using the src attribute of the iframe, there didn't seem to be much I could do to get that to work so we went back to the drawing board.

We ended up deciding to pull a PDF from SSRS and throw that up in our Angular application directly. Since we needed the ability to print and download the report anyway, using a PDF made plenty of sense. The only concern we really had was that any interactivity in the report would be lost when SSRS rendered it into a PDF (i.e. we'd lose sub-reports and click-throughs). This new direction, of course, introduced a new issue: how do we display a PDF report inside an Angular application?

The good news is that there's already an open-source component specifically for this. All we needed to do was get the report bytes from the server and give them over to the component. I setup my API to return a FileContentResult containing the PDF bytes, with a content type of 'application/pdf':

   1:  [HttpGet("/api/report")]
   2:  public async Task<IActionResult> GetReport(int id)
   3:  {
   4:      var client = new ReportExecutionServiceSoapClient(
   5:          ReportExecutionServiceSoapClient.EndpointConfiguration.ReportExecutionServiceSoap,
   6:          "http://<ssrs-server>/reportserver/reportexecution2005.asmx");
   7:  
   8:      LoadReportResponse loadedReport = await client.LoadReportAsync(new TrustedUserHeader(), "path/to/report", null);
   9:      ParameterValue parameter = new ParameterValue {Name = "ReportID", Value = id};
  10:      await client.SetExecutionParametersAsync(loadedReport.ExecutionHeader, new TrustedUserHeader(), new[] {parameter}, "en-us");
  11:      RenderRequest renderRequest = new RenderRequest(loadedReport.ExecutionHeader, new TrustedUserHeader(),
  12:          "PDF", @"<DeviceInfo><Toolbar>False</Toolbar></DeviceInfo>");
  13:      RenderResponse renderResponse = await client.RenderAsync(renderRequest);
  14:  
  15:      return new FileContentResult(renderResponse.Result, "application/pdf");
  16:  }

Once my API was returning the bytes of the PDF I just needed to get them in my Angular application and provide them to the component. I wrote about a little bit of difficulty doing this before so check out that (really short) post if you're confused. Here's the Angular code I ended up with (this is in a service):
   1:  getReport(id: number) {
   2:    const headers = {
   3:      'Content-Type': 'application/json',
   4:      'Accept': 'application/pdf'
   5:    };
   6:  
   7:    return this.httpClient.get(`http://<api-endpoint>/api/report/{id}`, {headers, responseType: arrayBuffer});
   5:  }

Now I had a way to get the bytes from the API into my angular application so all I had left to do was throw those bytes into the component. In the ngOnInit function of my component, I used the service to retrieve the report and bound the resulting ArrayBuffer to a property my component:
   1:  ngOnInit(): void {
   2:    this.reportService.getReport.subscribe(b => this.reportBytes = new Uint8Array(b));
   3:  }

Obviously, I needed to create a property called reportBytes on my component before this would work:
   1:  public reportBytes: Uint8Array;

Finally, I needed to use the 3rd party component in my markup. There are several options you can specify on this particular component. For the most part our implementation was simple and it actually worked just fine when we only provided the PDF bytes to display:
<pdf-viewer [src]="reportBytes"></pdf-viewer>

That was the last piece I had to put in place to render a report - in PDF format - in my Angular application. Of course, this would have worked just as well with any PDF so it's a nice reusable solution. With this last piece I can solidly check another item off my list.
  1. Display a list of all reports in SSRS
  2. Allow the user to click on a single report to view the report
  3. Allow the user to print the report
  4. Allow the user to download the report
Update: All four parts of this series are complete now.
  1. SSRS Reports in an Angular App (Part 1 - Showing the Report)
  2. SSRS Reports in an Angular App (Part 2 - Showing the Report (correctly))(this post)
  3. SSRS Reports in an Angular App (Part 3 - Printing the Report)
  4. SSRS Reports in an Angular App (Part 4 - Downloading the Report)

No comments:

Post a Comment