Migrating Crystal Reports from VB6 to DevExpress on Blazor

by Oscar Poveda, on Oct 18, 2026, 12:15:00 AM

A Hybrid Deterministic + AI Pipeline

Reports have always been the part of a legacy migration that the tooling leaves behind. Forms, modules and data-access code get transformed automatically, while reports are handled separately, often by hand. When we ran a batch of legacy Crystal Reports through a standard IDE import, about 90% came out wrong and fixing each one by hand took several hours. With the pipeline below, final review dropped to about ten minutes per report. This article explains why reports got left behind and then lays out the pipeline. It covers Crystal Reports stored in VB6 designer files to DevExpress Reporting on Blazor, and it extends to Delphi.

Why migrators never handled reports

The root cause is the source format. In VB6, reports often live in .dsr files, which are ActiveX designer files. A .dsr can contain a Crystal report, a Data Report, or some other designer. The extension says very little about what is inside, so a rule-based transformer has no stable grammar to target. The practical decision was to migrate the application code and deal with reports per project, once a real inventory showed up.

Generative AI changes this. Decisions that used to require fixed heuristics, such as how to map an element with no direct equivalent or how to correct a layout that came through wrong, can now be made case by case. That is what lets the pipeline below go from one report technology to another, not just from an old version of Crystal to a newer one.

Step 1: Classify the source and target by report paradigm

Before choosing a target, identify the structural model of the source reports. Crystal Reports uses a banded model. A report is built from bands (report header, page header, group sections, footers) and any number of detail sections, plus subreports for nested data.

Targets that share this model make conversion much easier:

  • DevExpress Reporting and Telerik Reporting are both banded. Mapping is not one-to-one, but the concepts line up: bands map to bands, details to details, and subreports exist on both sides. This lets you define a stable mapping pattern rather than redesign each report.
  • Microsoft Reporting (SSRS/RDL) uses a region-based layout model (tablix and other data regions) rather than bands, so bands and detail sections cannot be assumed. Conversion becomes redesign, with more judgment needed per report.

The differences that remain between banded engines are mostly at the edges. These include how the report connects to its data source, how functions and expressions are written, and how subreports are wired in. The mapper has to handle these explicitly.

Validate the target with a small sample. Don't decide based on documentation alone. In one experiment, we converted five representative Crystal reports to both candidate targets. DevExpress handled four of the five cleanly, while Telerik handled two, and the manual cleanup on the Telerik output was much larger. A five-report test is cheap and gives you evidence about which target's model is closest to your source.

Step 2: Don't rely on vendor "open and save" upgrade paths

For legacy Crystal (old .rpt files tied to the ActiveX runtime), the supported upgrade path is to install Crystal Reports for .NET developer tooling, open the old report in Visual Studio, and save it. In practice, format inconsistencies and errors are common. In one case with about twenty legacy reports, none converted cleanly, and all of them were rebuilt by hand.

The same pattern shows up when importing .rpt files into DevExpress through Visual Studio. In a batch we ran manually, about 90% of the reports came out wrong. Typical defects included:

  • headers missing or partially rendered
  • fields silently dropped (email, phone number)
  • elements shifted out of position (dates, addresses, web URLs)
  • rule lines lost on almost every report, which was the most consistent failure

Treat a built-in importer as one step in a pipeline, not as the migration.

Step 3: Build a deterministic conversion chain

The first half of the pipeline is fully deterministic: scriptable, repeatable, and with no model in the loop at runtime. AI can help you write this code faster, but nothing here needs probabilistic decisions.

In our reference project, the inventory was about 200 Crystal-based reports, 177 of which made up the processed set. They did not arrive as .rpt files; they arrived as VB6 designer files (.dsr and .dsx). The chain looked like this:

  1. Normalize the source format. A Python script extracts the report definitions from the .dsr and .dsx designer files and produces standard .rpt files. This turned out to be the easiest step.
  2. Batch import to the target. A tool imports every .rpt into DevExpress report definitions automatically, instead of one at a time in Visual Studio.
  3. Assemble the target project. All converted reports are placed in one Blazor solution, so they can be rendered and inspected together.

These steps ended up as one tool with multiple stages, not separate utilities. That keeps the whole chain re-runnable from source to target whenever a mapping rule changes.

Without AI, this is roughly where a migration would have stopped. You get every report in the target format, but with the same defects as a manual import, now arriving in bulk. When a deterministic import gets something wrong, the only fallback is to open the file and fix it by hand. At several hours per report, that doesn't scale to hundreds of reports.

Step 4: Add an AI-driven correction loop

The second half of the pipeline handles what deterministic rules can't. Its core idea is that a human observation about one report becomes a correction applied to the whole corpus.

Render original and converted reports side by side

The review application loads every converted report and renders it next to the original. Reports are filled with generated test data from a database, so layout problems show up even without production data.

Track state per report

Each report has a status:

  • Review: converted, but with known or suspected defects
  • Approved: checked side by side against the original and confirmed to match

Capture defects as short notes

The reviewer goes through the queue and attaches a short note to a specific report, for example:

  • "Missing the header."
  • "Email and phone number are not rendered."
  • "The date should be further to the right."
  • "Rule lines are missing; add them so they match the original."

Each note is written against one report, but it describes a defect class, not a single fix. That is what makes it reusable.

Apply notes in batches across the corpus

When around ten notes have built up, they go to the AI model as a batch, with an instruction like: verify these notes and fix the issues across all reports. The model does two things:

  1. It fixes the reports the notes were written on.
  2. It finds other reports with the same defect and applies the same fix to them.

The rule-lines case shows this well. The deterministic import had dropped rule lines from almost every report. One note, plus an instruction to restore the lines so they match the originals, was enough. A single run across the corpus put them back everywhere, and reports that had been broken in the same way came back correct without anyone looking at them individually.

Iterate and approve

The loop repeats: review, write notes, run a batch correction, re-render. Each pass moves more reports from Review to Approved. Approval stays a human decision based on a side-by-side comparison with the original. Toward the end, most remaining issues are small positional fixes (an address field a few units off, for example), and the final review takes about ten minutes per report.

Design principles behind the pipeline

A few principles are what make this approach work, and they apply beyond Crystal and DevExpress.

Deterministic where the transformation is known, probabilistic where it isn't. Format normalization, batch import and project assembly are deterministic and re-runnable. The model is used only for the residual defects that rules can't anticipate.

The model generalizes defects; it doesn't translate reports. You never ask the model to convert a report from scratch. You ask it to take a specific, human-confirmed observation and apply it across hundreds of artifacts. This keeps its output bounded and checkable.

Keep humans in the verification step. Notes come from people looking at rendered output, and approval comes from a side-by-side check. The model speeds up correction but doesn't decide what "correct" means.

Iterate from the first day. A working first conversion should exist on day one. Everything after that is refinement of mapping rules and correction rounds. In practice, building the tool takes about a week and running and iterating it takes about another week, even for inventories of 130 to 180 reports. In a separate project, about 130 Crystal reports were moved to DevExpress Reporting with a very little manual cleanup.

Extending the pipeline to new source formats: Delphi

Delphi reports are a different case on the source side. They aren't separate files; they are embedded in the Delphi form definitions and produced by Delphi's built-in reporting system. There are no .rpt files and no Crystal involved.

Structurally, though, they follow the same banded model: bands, detail sections, columns. In effect, they are a simplified version of the same components found in Crystal. So the work is limited to the front of the pipeline:

  • New: a source parser that extracts report definitions from the Delphi form files into the pipeline's input.
  • Unchanged: the DevExpress mapping and generation, the Blazor assembly, the side-by-side review application, and the notes-driven correction loop.

DevExpress is the natural target here because the mapping knowledge already exists from the Crystal work. Telerik is a workable alternative, with a somewhat more manual cleanup expected.

More broadly, once the target side and the correction loop exist, a new legacy report format is a parser problem. And as long as the source and target share a structural model, the choice of target matters less and less.

Summary checklist

  1. Identify the source report paradigm (banded or not) and choose a target with the same structural model.
  2. Validate the target on a small representative sample before committing.
  3. Don't rely on vendor open-and-save or IDE import paths as the migration.
  4. Build a deterministic chain: normalize the source format, batch import, and assemble all reports into one target solution.
  5. Render original and converted reports side by side with generated test data.
  6. Record defects as short notes and track each report as Review or Approved.
  7. Send notes to the model in batches and have it apply each fix to every report with the same defect.
  8. Repeat until reports pass a human side-by-side check.
  9. For a new source technology, write a new parser and reuse everything after it.

If you have VB6 and want to know what it takes to move it to something modern like .NET 10 or .NET 11 - give us a shoutout

Topics:Visual BasicVB6Code Migrationcrystal reports

Comments

Subscribe to GAPVelocity AI Modernization Blog

FREE CODE ASSESSMENT TOOL