Skip to content

Report Viewer

To view reports generated by the system, you can use the ArilekhReport's ReportViewer. Currently, we have Report Viewer for Angular application.

Getting Started

In your angular application, please install the arilekh-report-viewer.

NPM

Please use below command line to your angular application

npm i arilekh-report-viewer

Setup in the Angular Application

Import necessary things in your component.ts file.

import { IReportViewer, PageResponse, RenderRequest, RenderResponse, ReportViewerApiService, ReportViewerComponent, SearchResponse, ThumbnailListResponse } from 'arilekh-report-viewer';

We should use IReportViewer to manage API endpoints for different request made by the report viewer. Please refer to the below code snippet for the implementation.

export class MyReportComponent implements IReportViewer {
  constructor(
    private reportApiService: ReportViewerApiService,
    private http: HttpClient,
    public themeService: ThemeService  // This is our custom service to manage theme in the application. Please use your own theme service or remove this if you don't have any theme service in your application.
  ) {

  }

  ngOnInit(): void {
    this.reportApiService.setProvider(this);
    this.reportApiService.setLoading(true);

    this.http.post(`${this.baseUrl}reports/generate/generate-sales`,{ Req: 'Test' }).subscribe({        
      next: (v:any) => {
        this.reportApiService.loadSession(v.sessionId).subscribe({
            next: res => {
                console.log('session loaded', res);
            },
            error: err => {
                console.error(err);
            }
        });;
        console.log('resolve', v, v.sessionId);
      },
      error: (e) => {
        console.log('error', e);
      }
    });
  }

  // Modify your endpoints here to match your backend API endpoints.

  render(req: RenderRequest): Observable<RenderResponse> {
    return this.http.post<RenderResponse>(`${environment.base_url}reports/viewer/render`, req)
  }
  getPage(sessionId: string, pageNumber: number): Observable<PageResponse> {
    return this.http.get<PageResponse>(`${environment.base_url}reports/viewer/${sessionId}/pages/${pageNumber}`)
  }
  getThumbnails(sessionId: string): Observable<ThumbnailListResponse> {
    return this.http.get<ThumbnailListResponse>(`${environment.base_url}reports/viewer/${sessionId}/thumbnails`);
  }
  search(sessionId: string, params: HttpParams): Observable<SearchResponse> {
    return this.http.get<SearchResponse>(`${environment.base_url}reports/viewer/${sessionId}/search`, { params });
  }
  loadSession(sessionId: string): Observable<RenderResponse> {
    console.log('>>>', this.http);
    return this.http.get<RenderResponse>(`${environment.base_url}reports/viewer/${sessionId}/info`);
  }
  renderServer(req: { reportXml: string; dataProviderKey: string; parameters?: Record<string, unknown>; ttlMinutes?: number; }): Observable<RenderResponse> {
    return this.http.post<RenderResponse>(`${environment.base_url}reports/viewer/render-server`, req);
  }

  closeReportViewer(){
    // Clear the report viewer session and reset the state
    // You can implement any additional logic here if needed
    this.reportApiService.empty();
  }
}

In your component.html file, you can use the arv-report-viewer component to display the report viewer. Please refer to the below code snippet for the implementation.

<arv-report-viewer
            reportTitle="Arilekh - Report Viewer"
            closeBtnText="Close Report"
            (closeBtn)="closeReportViewer()"
            [theme]="(themeService.readPreferredTheme()=='light')?'light':'dark'"
            style="height: 100%; display: block;">
</arv-report-viewer>

Setup in .NET Application

In your .NET application, please install the ArilekhReport.Core package.

dotnet add package ArilekhReport.Core

Then, add the following code in your Program.cs file to manage report session in .NET application.

using ArilekhReport.WebApi.Services;

builder.Services.AddSingleton<ReportSessionService>();

Create a new class named ReportDTO and add the following code to handle report viewer requests.

using ArilekhReport.Core.Model;
using ArilekhReport.Core.Rendering;

namespace ArilekhReport.WebApi.DTOs;  // <= modify your namespace according to your project structure
{
   public sealed class RenderResponse
    {
        public string SessionId { get; set; } = string.Empty;
        public int PageCount { get; set; }
        public float PageWidthPt { get; set; }
        public float PageHeightPt { get; set; }
        public string RenderedAt { get; set; } = string.Empty;
        public long RenderMs { get; set; }
    }

    public sealed class PageResponse
    {
        public int PageNumber { get; set; }
        public int TotalPages { get; set; }
        public float WidthPt { get; set; }
        public float HeightPt { get; set; }
        public List<ElementDto> Elements { get; set; } = [];
    }

    public sealed class ElementDto
    {
        public string Type { get; set; } = string.Empty;  // text | rect | line | ellipse | image
        public float X { get; set; }
        public float Y { get; set; }
        public float Width { get; set; }
        public float Height { get; set; }
        public float Rotation { get; set; }

        // text
        public string? Text { get; set; }
        public string? Alignment { get; set; }   // left | center | right
        public string? VerticalAlign { get; set; }   // top | middle | bottom
        public string? HyperlinkUrl { get; set; }

        // style (shared by text)
        public StyleDto? Style { get; set; }

        // rect
        public string? FillColor { get; set; }
        public string? StrokeColor { get; set; }
        public float StrokeWidth { get; set; }
        public float BorderRadius { get; set; }

        // line
        public float X2 { get; set; }
        public float Y2 { get; set; }

        // image
        public string? Src { get; set; }
        public string? Stretch { get; set; }
    }

    public sealed class StyleDto
    {
        public string? FontFamily { get; set; }
        public float? FontSize { get; set; }
        public bool Bold { get; set; }
        public bool Italic { get; set; }
        public bool Underline { get; set; }
        public string? ForeColor { get; set; }
        public string? BackColor { get; set; }
        public float PaddingLeft { get; set; }
        public float PaddingRight { get; set; }
    }


    public sealed class ThumbnailListResponse
    {
        public string SessionId { get; set; } = string.Empty;
        public int PageCount { get; set; }
        /// <summary>Text summary per page for thumbnail labels (first text element on page).</summary>
        public List<PageSummary> Pages { get; set; } = [];
    }

    public sealed class PageSummary
    {
        public int PageNumber { get; set; }
        public string FirstText { get; set; } = string.Empty;
        public string GroupLabel { get; set; } = string.Empty;
    }

    public sealed class SearchResponse
    {
        public string Query { get; set; } = string.Empty;
        public int Hits { get; set; }
        public List<SearchHit> Results { get; set; } = [];
    }

    public sealed class SearchHit
    {
        public int PageNumber { get; set; }
        public string Snippet { get; set; } = string.Empty;
        public float X { get; set; }
        public float Y { get; set; }
    }
}

Copy the designed report file (e.g., SampleReport.rdx) to your .NET application project. You can place it in a folder named Reports or any other location of your choice.

Then, create a new controller named ReportViewerController and add the following code to handle report viewer requests.

using ArilekhReport.Core.Rendering;
using ArilekhReport.WebApi.DTOs;
using Microsoft.AspNetCore.Mvc;

namespace ArilekhReport.WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ViewerController : ControllerBase
    {
        private readonly ReportSessionService _sessions;

        public ViewerController(ReportSessionService sessions)
        {
            _sessions = sessions;
        }


        [HttpGet("{id}/info")]
        public ActionResult<RenderResponse> Info(string id)
        {
            var doc = _sessions.GetDocument(id);
            if (doc is null) return NotFound(new { error = "Session not found or expired." });

            return Ok(new RenderResponse
            {
                SessionId = id,
                PageCount = doc.PageCount,
                PageWidthPt = doc.Pages.Count > 0 ? doc.Pages[0].Width : 595f,
                PageHeightPt = doc.Pages.Count > 0 ? doc.Pages[0].Height : 842f,
                RenderedAt = doc.RenderedAt,
            });
        }


        [HttpGet("{id}/pages/{pageNumber:int}")]
        public ActionResult<PageResponse> GetPage(string id, int pageNumber)
        {
            var doc = _sessions.GetDocument(id);
            if (doc is null) return NotFound(new { error = "Session not found or expired." });

            var page = _sessions.GetPage(id, pageNumber);
            if (page is null) return NotFound(new { error = $"Page {pageNumber} not found." });

            return Ok(ReportElementMapper.ToPageResponse(page, doc.PageCount));
        }



        [HttpGet("{id}/pages")]
        public ActionResult<List<PageResponse>> GetPages(
                    string id,
                    [FromQuery] int from = 1,
                    [FromQuery] int to = 10)
        {
            var doc = _sessions.GetDocument(id);
            if (doc is null) return NotFound();

            from = Math.Max(1, from);
            to = Math.Min(doc.PageCount, to);

            var result = new List<PageResponse>();
            for (int p = from; p <= to; p++)
            {
                var page = _sessions.GetPage(id, p);
                if (page is not null)
                    result.Add(ReportElementMapper.ToPageResponse(page, doc.PageCount));
            }
            return Ok(result);
        }


        [HttpGet("{id}/thumbnails")]
        public ActionResult<ThumbnailListResponse> GetThumbnails(string id)
        {
            var doc = _sessions.GetDocument(id);
            if (doc is null) return NotFound();

            var pages = doc.Pages.Select(p => new PageSummary
            {
                PageNumber = p.PageNumber,
                FirstText = p.Elements
                              .OfType<RenderedTextElement>()
                              .Select(e => e.Text)
                              .FirstOrDefault(t => !string.IsNullOrWhiteSpace(t)) ?? "",
                GroupLabel = "",   // extended: could extract from group header sections
            }).ToList();

            return Ok(new ThumbnailListResponse
            {
                SessionId = id,
                PageCount = doc.PageCount,
                Pages = pages,
            });
        }


        [HttpGet("{id}/search")]
        public ActionResult<SearchResponse> Search(string id, [FromQuery] string q = "")
        {
            if (string.IsNullOrWhiteSpace(q))
                return BadRequest(new { error = "Query 'q' is required." });

            var doc = _sessions.GetDocument(id);
            if (doc is null) return NotFound();

            var hits = new List<SearchHit>();
            foreach (var page in doc.Pages)
            {
                foreach (var el in page.Elements
                    .OfType<RenderedTextElement>())
                {
                    if (el.Text.Contains(q, StringComparison.OrdinalIgnoreCase))
                    {
                        // Highlight snippet
                        var idx = el.Text.IndexOf(q, StringComparison.OrdinalIgnoreCase);
                        var start = Math.Max(0, idx - 20);
                        var snippet = (start > 0 ? "…" : "") +
                                      el.Text.Substring(start, Math.Min(60, el.Text.Length - start)) +
                                      (start + 60 < el.Text.Length ? "…" : "");

                        hits.Add(new SearchHit
                        {
                            PageNumber = page.PageNumber,
                            Snippet = snippet,
                            X = el.X,
                            Y = el.Y,
                        });
                        break; // one hit per page per element is enough for nav
                    }
                }
            }

            return Ok(new SearchResponse
            {
                Query = q,
                Hits = hits.Count,
                Results = hits,
            });
        }



        [HttpGet("delete/{id}")]
        public IActionResult Release(string id)
        {
            // Sessions auto-evict; explicit delete just frees memory earlier
            // (We expose RemoveSession from the service for explicit calls)
            return NoContent();
        }
    }
}

In the angular, we will generate the report in .NET application and return the sessionId to the angular application. Then, we will use the sessionId to load the report in the report viewer. In .NET, the generated report will be held in memory for certain time limit. We can delete the session explicitly to free up memory earlier. Please refer to the below code snippet for the implementation.

using ArilekhReport.Core.Rendering;
using ArilekhReport.WebApi.DTOs;
using Microsoft.AspNetCore.Mvc;

namespace ArilekhReport.WebApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class GenerateController : ControllerBase
    {
        private readonly ReportSessionService _sessions;

        public GenerateController(ReportSessionService sessions)
        {
            _sessions = sessions;
        }


        // Create the model for the request body based on your requirements. 
        public class SalesRequest
        {
            public string Req {  get; set; }
        }

        // Here, we are using sample data to generate the report. You can replace the sample data with your own data source as needed.
        private DataTable LoadData(){
            var dataTable = new DataTable();
            dataTable.TableName = "Orders"; // This should match the DataSource name defined in the Report Designer
            dataTable.Columns.Add("OrderId", typeof(int));
            dataTable.Columns.Add("CustomerName", typeof(string));
            dataTable.Columns.Add("Amount", typeof(decimal));
            dataTable.Columns.Add("OrderDate", typeof(DateTime));

            dataTable.Rows.Add(1, "John Doe", 100.0m, new DateTime(2023, 1, 15));
            dataTable.Rows.Add(2, "Jane Smith", 200.0m, new DateTime(2023, 2, 20));
            dataTable.Rows.Add(3, "Alice Johnson", 300.0m, new DateTime(2023, 3, 10));
        }


        [HttpPost("generate-sales")]
        public async Task<IActionResult> GenerateSales([FromBody] SalesRequest req)
        {
            var report = XmlReportSerializer.LoadReport(_env.ContentRootPath + Path.DirectorySeparatorChar + "Reports" + Path.DirectorySeparatorChar + "SampleReport.rdx");

            DataTable dataReport = LoadData();

            var provider = new DataTableProvider();
            provider.Register(dataReport);

            var engine = new ReportEngine();
            var doc = await engine.RenderAsync(report, provider);
            var guid = _sessions.StoreDocument(doc, ttlMinutes: 30); // You can mention the time limit for the session in minutes. Here, we have set it to 30 minutes.

            return Ok(new { sessionId = guid });
        }
    }
}

Based on your needs, you can send necessary parameters in the request body to generate the report dynamically. The above code snippet is just a sample implementation to demonstrate how to generate a report and return the sessionId to the Angular application. You can modify it according to your requirements.