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. |
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.

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!