Skip to main content Skip to footer

Building a Locked Down Web Report Designer With ActiveReports.NET

Quick Start Guide
What You Will Need

ActiveReports.NET (trial or developer license)

Visual Studio 2019-2022

Controls Referenced Web Designer
Tutorial Concept Build a streamlined, browser-based report designer in ActiveReports.NET by customizing the WebDesigner to hide advanced tools, limit editable properties, and prevent changes to read-only templates. Follow along with real sample code to combine client-side configuration and server-side safeguards for a secure, simplified report-authoring experience.

lock down designer

ActiveReports.NET includes a powerful browser‑based report designer. In its default state, the designer exposes every tool, property and data connection. If you need to provide report authoring to non‑technical users, a fully open interface can be overwhelming or risky. This how‑to guide walks through the WebDesigner_Custom sample and shows you how to build a locked down report designer. The steps below use real code from the sample so you can follow along in your own project.

In this tutorial, we'll cover:

Want to Try for Yourself? Download ActiveReports.NET Today!

Step 1: Create the UI

Start by creating a simple HTML page to host the designer and a list of reports. The sample’s index.html defines a sidebar for selecting reports and a main container for the designer:

<!-- index.html -->
<div class="page-container">
  <div class="sidebar-container">
    <div class="sidebar-header">Select report</div>
    <ul class="navbar" id="reportsList"></ul>
  </div>
  <div id="ar-web-designer" class="ar-web-designer">
    <span class="ar-web-designer__loader"><b>Loading…</b></span>
  </div>
</div>
<script type="module" src="script.js"></script>

The #ar-web-designer element is where the designer will appear, and reportsList will be filled with report names at runtime. You can style this page to match your own application.

Step 2: Initialize the Designer and Hide Features

Next, create a script.js file and import the designer module. The key call is arWebDesigner.create, which renders the designer and accepts a settings object. This object controls which parts of the interface are visible:

// script.js (simplified)
import { arWebDesigner } from './web-designer.js';
import { createViewer } from './jsViewer.min.js';

// fetch the list of reports and then create the designer
getReports().then(reports => {
  arWebDesigner.create('#ar-web-designer', {
    server: { url: serverUrl },
    document: { id: reports[0].Id, type: { type: 'report', platform: 'rdlx' } },
    // hide buttons on the app bar
    appBar: {
      aboutButton: { visible: false },
      parametersTab: { visible: false },
      contextActionsTab: { visible: false }
    },
    // expose a basic property grid
    propertyGrid: { mode: 'Basic' },
    // limit font choices
    fonts: [
      { header: 'Questionable Choice' },
      { label: 'Pretty Font', value: 'Comic Sans MS' },
      { header: '' },
      'Arial',
      'Courier New',
      'Times New Roman'
    ],
    // disable the data tab so users cannot add data sets or sources
    data: { dataTab: { visible: false } },
    // customise the menu: hide the toolbox and layer editor and insert your own logo
    menu: {
      logo: { custom: { type: 'css', class: 'example-icon' } },
      toolBox: { visible: false },
      layerEditor: { visible: false }
    },
    // hide the properties mode button in the status bar
    statusBar: { propertiesModeButton: { visible: false } },
    // lock the layout so panels can’t be rearranged
    lockLayout: true,
    // remove sensitive properties from the property grid
    filterProperties: (descriptors, item, platform) => {
      return descriptors.filter(d => d.valuePath !== 'Value' &&
                                     d.valuePath !== 'Name' &&
                                     d.valuePath !== 'Style.Format' &&
                                     d.category !== 'propertyDescriptors:categories.layout');
    },
    // intercept save events and prevent overwriting read‑only reports
    documents: {
      fileView: { visible: false },
      handlers: {
        onBeforeSave: (info) => {
          const report = reportsList.find(r => r.Id === info.name);
          if (report?.Readonly) {
            const designer = GrapeCity.ActiveReports.Designer.apiOf('ar-web-designer');
            designer.notifications.warning(
              'The report cannot be overwritten.',
              "Original report cannot be changed, use 'Save As' with new report name."
            );
            return false;
          }
          return true;
        },
        onAfterSave: (info) => {
          // refresh the report list after save and highlight the saved item
          getReports().then(updated => {
            fillReportsList(updated);
            selectReportElement(info.id);
          });
        }
      }
    },
    // provide a preview window using the JS Viewer component
    preview: {
      openViewer: (options) => {
        if (viewer) {
          viewer.theme = options.theme;
          viewer.openReport(options.documentInfo.id);
          return;
        }
        viewer = createViewer({
          element: '#' + options.element,
          reportService: { url: 'api/reporting' },
          reportID: options.documentInfo.id,
          settings: { zoomType: 'FitPage' },
          themes: { initialTheme: options.theme.name }
        });
      }
    }
  });
});

This configuration hides the designer’s about button, parameters panel and context actions, switches the property grid to Basic mode, disables the data tab and restricts fonts. The lockLayout option freezes the layout, while the filterProperties function removes sensitive properties like Value, Name and Style.Format. Save events are trapped to prevent overwriting read‑only templates and to refresh the report list on success. The embedded preview uses the JS Viewer component.

Your script should also fetch the list of reports and populate the sidebar. In the sample, getReports calls a REST endpoint (/reports), loops through the returned JSON and appends list items to #reportsList. Clicking an item calls designer.documents.openById to load it. A helper function toggles a CSS class to highlight the selected report.

Step 3: Store Reports Securely on the Server

On the back end, reports are stored in a SQLite database using Entity Framework Core. The ReportsDbContext defines a Reports table and points to a database file under the resources folder. The primary key (Id) is not auto‑generated so that report names remain stable identifiers. The report entity includes a binary Content column and a Readonly flag:

// Data/ReportDbContext.cs (excerpt)
public class ReportsDbContext : DbContext
{
    public DbSet<Report> Reports { get; set; }
    private string _dbPath;
    public ReportsDbContext()
    {
        _dbPath = Path.Combine(Directory.GetCurrentDirectory(), "resources", "Storage.db");
    }
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlite($"Data Source={_dbPath}");
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Report>().HasKey(e => e.Id);
        modelBuilder.Entity<Report>().Property(e => e.Id).ValueGeneratedNever();
        base.OnModelCreating(modelBuilder);
    }
}

public class Report : ReportInfo
{
    public byte[] Content { get; set; }
    public bool Readonly { get; set; }
}

The ReportService encapsulates CRUD operations. Its SaveReport method illustrates how read‑only templates are protected: it checks for an existing record and throws an exception if the report is marked Readonly; otherwise, it updates or adds the record and calls SaveChanges. Deleting a report simply removes it and saves the changes:

// Services/ReportService.cs (excerpt)
public void SaveReport(Report report)
{
    var reportInfo = _reportsDbContext.Reports.FirstOrDefault(r => r.Id == report.Id);
    if (reportInfo != null)
    {
        if (reportInfo.Readonly)
            throw new Exception("Original report cannot be changed, use 'Save As' with new report name");
        reportInfo.Content = report.Content;
    }
    else
    {
        _reportsDbContext.Reports.Add(report);
    }
    _reportsDbContext.SaveChanges();
}

Temporary reports, such as when a user saves a copy of a report, are handled by a ReportStore implementation. When the SaveSettings.IsTemporary flag is set, the SaveReport method generates a new GUID filename and stores the report in an in‑memory dictionary instead of the database:

// Implementation/ReportStore.cs (excerpt)
public string SaveReport(ReportType reportType, string reportId, Stream reportData, SaveSettings settings = SaveSettings.None)
{
    var newReport = new Report
    {
        Id = (settings & SaveSettings.IsTemporary) != 0 ? $"{Guid.NewGuid()}.rdlx" : reportId,
        Name = reportId,
        Content = reportData.ToArray(),
        ReportType = reportType
    };
    if ((settings & SaveSettings.IsTemporary) != 0)
        _tempReportStorage.Add(newReport.Id, newReport);
    else
        _reportService.SaveReport(newReport);
    return newReport.Id;
}

The DesignController exposes the /reports endpoint that returns all stored reports. Your front end calls this endpoint via fetch('reports') to populate the list of available reports.

Finally, a ResourceProvider supplies static files (for example, images) to the designer. It looks up a file in the resources folder and returns an open file stream. The ListResources and DescribeResources methods are empty because this sample does not enumerate resources.

locked down designer
List of selectable reports (left), Limited editable properties and options (right)

Conclusion

This tutorial demonstrates how to combine client‑side configuration and server‑side safeguards to produce a controlled report‑authoring experience. By hiding advanced UI elements, limiting property editing and intercepting saves, you protect templates from accidental changes. The back end further secures your data by using read‑only flags and temporary storage. With ActiveReports.NET, you are free to expose exactly the right level of functionality for your users. Feel free to clone the WebDesigner_Custom sample and modify it to match your own requirements.

Want to Try for Yourself? Download ActiveReports.NET Today!

Tags:

comments powered by Disqus