Integrate DevExpress Report Viewer and Report Designer into Blazor Server, Blazor WebAssembly, and Blazor Web App. Register AddDevExpressServerSideBlazorReportViewer, AddDevExpressBlazorReporting in Program.cs. Configure DxResourceManager.RegisterScripts in App.razor. Components: DxReportViewer, DxDocumentViewer, DxReportDesigner, DxWasmDocumentViewer, DxWasmReportDesigner. Load reports via OpenReportAsync, ReportName. Customize toolbar, tab panel, parameter editors. Implement IReportProvider, ReportStorageWebExtension. Troubleshoot blank viewer, "Service not registered", missing render mode, Skia DllNotFoundException. Choose between native, JS-based, and WASM component families.
Instalação
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Integrates DevExpress report viewing and designing into Blazor applications. Five components across three families cover every Blazor hosting model.
This skill covers Blazor UI component integration only. For creating reports programmatically (bands, controls, data binding, export), use the devexpress-reports-core skill.
Skill boundary — hard stop: This skill does NOT generate XtraReport subclasses, band declarations (DetailBand, GroupHeaderBand), or control code (XRLabel, XRTable, etc.). If a report class is needed, switch to devexpress-reports-core for that part, get the class name or instance, then return here to wire it into the viewer.
Agent Workflow — Three Required Phases
Do not write any code until all three phases are complete. This ensures you load the correct reference files and generate a single coherent step-by-step plan.
Phase 1 — Search the project and ask clarifying questions for any unknowns
Phase 2 — Apply the decision gates to select the exact component and architecture
Phase 3 — Load the matching reference files, merge their steps, present the plan, then execute
Phase 1 — Preflight: Gather Context
Step 1 — Automated Discovery (Do Before Asking Anything)
Run these searches in the project before asking the developer:
Installed packages: dotnet list package → look for DevExpress.Blazor.Reporting.* and their versions
Blazor configuration classification — follow this 4-step algorithm and record the result before selecting any component:
Step A — Solution structure: Find all .csproj files. Is there a .Client project (name suffix or a <ProjectReference> from a server project pointing to a client project)?
Step B — Server Program.cs (look for AddRazorComponents call chain):
What is found
Preliminary result
AddRazorComponents() with no AddInteractiveServerComponents() or AddInteractiveWebAssemblyComponents()
→ Static
AddInteractiveServerComponents() only
→ Interactive Server
AddInteractiveWebAssemblyComponents() only
→ Interactive WebAssembly (Client + Server)
Both AddInteractiveServerComponents() + AddInteractiveWebAssemblyComponents()
→ Auto candidate; confirm in Step D
Step C — Client Program.cs: presence of WebAssemblyHostBuilder.CreateDefault(args) confirms a WASM client project. If this file exists without a server Program.cs that calls MapRazorComponents → Interactive WebAssembly Standalone.
Step D — @rendermode in .razor files (search App.razor, Routes.razor, all @page components):
Directive / parameter
Indicates
@rendermode InteractiveServer
Interactive Server
@rendermode InteractiveWebAssembly
Interactive WebAssembly
@rendermode InteractiveAuto or new InteractiveAutoRenderMode(...)
Interactive Auto
No directives and no AddInteractive* in Program.cs
Static
Key rule: Both AddInteractiveServerComponents() + AddInteractiveWebAssemblyComponents() in Program.cs does not alone mean Auto mode. Final classification uses the @rendermode directives actually present in components.
Final result — record one of: Static | Interactive Server | Interactive WebAssembly Standalone | Interactive WebAssembly (Client + Server) | Interactive Auto. If ambiguous, ask the user for the Visual Studio template name or the dotnet new command used to create the project.
Existing viewer/designer components: search all .razor files for <DxReportViewer, <DxDocumentViewer, <DxReportDesigner, <DxWasmDocumentViewer, <DxWasmReportDesigner — note component names and file locations
Report sources and name resolution: search for new XtraReport(), IReportProvider, IReportProviderAsync, ReportStorageWebExtension, OpenReportAsync, Report="@, ReportName=", XtraReport loading methods (FromXmlStream, LoadLayoutFromXml, etc.) calls.
Data and connection strings: search appsettings.json for connection strings; search .cs files for custom data record classes used as report data sources
Navigation menu structure:
Search for navigation components: NavMenu.razor, NavBar.razor, or any .razor file with <NavLink> tags
Typical location: Components/, Layouts/, or root
Record the exact file path and one example <NavLink> element (copy its attributes, CSS classes, icon structure)
Identify the pattern: flat list vs. grouped categories
Record the route naming convention used (e.g., /viewer, /reports, /designer)
(Custom data source discovery) — only if a document viewer with storage or a designer component is found or requested:
Search report class files for [DataSource], ObjectDataSource, List<T>, IList properties
Extract class names used as data sources (e.g., SalesData, ProductRecord)
Record which classes need trust registration (see Phase 2 Decision Gate F)
Record findings. Use them to answer questions below without prompting the developer.
After Discovery: New Component Evaluation
Apply this evaluation immediately after completing items 1–5 above — before asking any clarifying questions.
Using the results from item 3 above, determine:
If existing reporting components were found (item 3) AND the user is requesting a new component (different from what is already there):
Record the new component name explicitly — e.g., "User is adding DxReportDesigner; project already has DxReportViewer + DxWasmDocumentViewer"
Do NOT assume the existing setup applies — each component has distinct requirements. For example:
Phase 1 Step 1 item 2 result is ambiguous; apply Q1 script below
Q2
Component needed: Native Viewer or JS-based Viewer?
User said "viewer" without specifying; apply Q2 script below
Q3
Report source: existing report class, by name via a service, or a new sample report is needed?
No report class, IReportProvider, or ReportStorageWebExtension found in project; apply Q3 script below
Q4
Target project for new page: In multi-project WASM setups, which project should receive the new page — server or client? Also needed for Phase 3 Step 3B navigation link addition.
User requested a new page in a multi-project solution (WASM Client+Server or Interactive Auto); apply Q4 script below
Q1 Script — Ambiguous Blazor Configuration
When Phase 1 Step 1 item 2 (4-step classification algorithm) cannot determine the hosting mode unambiguously, ask the developer directly to identify their target configuration and the project for component integration:
"I couldn't auto-detect your Blazor configuration. Which hosting model applies?
Static / Interactive Server / Interactive WebAssembly Standalone / Interactive WebAssembly (Client + Server) / Interactive Auto
For multi-project options (Client + Server or Auto): should the viewer/designer page go in the Server or Client project?"
Use the developer's responses to immediately confirm the hosting mode and target project, bypassing the need for further investigation.
Q2 Script — Viewer Ambiguity
When the user requests "a report viewer" without naming a component, present this choice explicitly:
"There are two viewer options — which fits your needs?
Native Blazor Viewer (DxReportViewer) — pure C# customization, no JavaScript required, works on Blazor Server and WebAssembly
When no report class, IReportProvider, or ReportStorageWebExtension is found:
"No reports were found in the project. How would you like to provide one?
Create a new sample report — I'll use the devexpress-reports-core skill to generate a report class
Use an existing .repx file — point me to it and I'll wire it in via a report storage or provider
Implement a report provider — I'll create the report myself"
If the developer chooses option 1: immediately load the devexpress-reports-core skill (or launch a subagent using that skill if the environment supports runSubagent). Confirm the generated report class name with the developer before proceeding with viewer integration. Do not proceed until a report class name is available.
Q4 Script — Target Project for New Page in Multi-Project Solutions
When the user requests a new page to be added to the project and the configuration is Interactive WebAssembly (Client + Server) or Interactive Auto, confirm the target project. Use the automatic assignment table from Decision Gate D if the component family is already known, or ask explicitly:
"Your project has a server project ([ServerProjectName]) and a client project ([ClientProjectName]). Which project should receive the new viewer/designer page?
Server project — for DxWasmDocumentViewer, DxWasmReportDesigner, DxReportViewer (not available in Interactive Auto) or if you prefer server-side report processing
Client project — for DxDocumentViewer, DxReportDesigner, DxReportViewer (not available in Interactive Auto) or if you prefer client-side WASM execution"
If the component is already determined (e.g., user explicitly said "DxDocumentViewer"), use the automatic assignment from Decision Gate D and confirm:
"For DxDocumentViewer in Interactive WebAssembly mode, the page belongs in [ClientProjectName]. Proceed?"
Use the Blazor configuration result from Phase 1, Step 1 item 2. Before selecting any component, apply the matrix below to filter the valid options.
Hosting Mode × Component Compatibility
Component
Static
Interactive Server
WASM Standalone
WASM Client+Server
Interactive Auto
DxReportViewer
❌
✅
✅
✅
❌ not supported
DxDocumentViewer
❌
✅ (server)
✅
✅ .Client project only
✅ .Client project only
DxReportDesigner
❌
✅ (server)
✅
✅ .Client project only
✅ .Client project only
DxWasmDocumentViewer
❌
❌
❌
✅ server project only
✅ server project only
DxWasmReportDesigner
❌
❌
❌
✅ server project only
✅ server project only
If Static: No DevExpress reporting component works in static render mode. Explain the limitation and suggest enabling Interactive Server mode (AddInteractiveServerComponents() + @rendermode InteractiveServer on the page).
If DxReportViewer + Interactive Auto: Incompatible combination. Explain the limitation and offer DxDocumentViewer (in .Client) or DxReportDesigner (in .Client) as alternatives. Do not proceed with DxReportViewer integration.
If Interactive WebAssembly (Client + Server) or Interactive Auto: After selecting the component, proceed to Decision Gate D to confirm the target project before writing any code.
Component Family Overview
DxReportViewer
DxDocumentViewer
DxReportDesigner
DxWasmDocumentViewer
DxWasmReportDesigner
Type
Native Blazor
JS-based
JS-based
JS-based (WASM)
JS-based (WASM)
Purpose
Viewer
Viewer
Designer + embedded Viewer
Viewer
Designer + embedded Viewer
Render mode
Server or WebAssembly
Server or Standalone WASM
Server or Standalone WASM
WebAssembly only
WebAssembly only
Customization
C# callbacks
JavaScript callbacks
JavaScript callbacks
JavaScript callbacks
JavaScript callbacks
Requires MVC controllers
No
Yes
Yes
Yes
Yes
NuGet
Blazor.Reporting.Viewer
JSBasedControls + AspNetCore.Reporting
Server: same as Viewer; WASM: JSBasedControls + Skia
Client: JSBasedControls.WebAssembly
same as DxWasmDocumentViewer
Do not mix API families.OnCustomizeToolbar/ExportModel are C# and work only on DxReportViewer. CustomizeMenuActions/CustomizeExportOptions are JavaScript callbacks and work only on JS-based components. Crossing families causes silent failures or undefined errors.
Decision Gate A — User Need → Component
User need
Component
View reports with C# customization
DxReportViewer (native)
View reports with JavaScript events / mobile support
DxDocumentViewer (or DxWasmDocumentViewer)
End-user report design and saving with an embedded viewer
Decision Gate D — Target Project in Multi-Project Solutions
Apply this gate when the configuration is Interactive WebAssembly (Client + Server) or Interactive Auto and a component has been selected.
Automatic assignment (propose and confirm — no open question needed)
Component
Target project for .razor page
Service registration
Reason
DxDocumentViewer
.Client project
.ClientProgram.cs
Must execute in the browser WASM context
DxReportDesigner
.Client project
.ClientProgram.cs
Must execute in the browser WASM context
DxWasmDocumentViewer
.Client project
Server Program.cs
Component runs in browser (.Client); backend services and HTTP endpoints on server
DxWasmReportDesigner
.Client project
Server Program.cs
Component runs in browser (.Client); backend services and HTTP endpoints on server
For automatically assigned cases, present a confirmation before proceeding:
For DxDocumentViewer or DxReportDesigner:
"For DxDocumentViewer / DxReportDesigner in Interactive WebAssembly / Auto mode, the component and its service registration go in [ClientProjectName]. Proceed?"
For DxWasmDocumentViewer or DxWasmReportDesigner:
"For DxWasmDocumentViewer / DxWasmReportDesigner in Interactive WebAssembly / Auto mode, the .razor page goes in [ClientProjectName] (component runs in the browser), but service registration and controllers go in the server Program.cs. Proceed?"
When the target project is ambiguous
If the user's project structure or component choice does not map unambiguously to the table above, ask:
"Detected [configuration type] with two projects:
Server: [ServerProjectName]
Client: [ClientProjectName]
[ComponentName] can be added to [server / client / either] project. Which project should receive the component?"
``
Decision Gate F — Custom Data Source Mapping for Deserialization
Apply when a designer component is selected, OR a viewer component with ReportStorageWebExtension is being used.
Purpose: Identify custom data source classes needing trust registration for report deserialization (both designer save/load/preview and viewer storage access).
When to apply: (DxReportDesigner / DxWasmReportDesigner / DxDocumentViewer / DxWasmDocumentViewer with ReportStorageWebExtension) is being added AND Phase 1 Step 1 item 7 found custom data source classes.
Quick check:
List data source classes from Phase 1 item 7 discovery
For each: custom class? Used in reports? Both type and array variant needed?
Record decision: "Classes needing trust: [list]"
Why: Unregistered custom types cause NonTrustedTypeDeserializationException at runtime when:
Designer components save/load/preview reports containing custom data sources
Viewer components load reports from storage (ReportStorageWebExtension or IReportProvider returning REPX)
Mixed Scenario — Viewer + Designer in the Same Project
Load reference files for both scenarios. Identify shared setup steps (Program.cs service registration, App.razor scripts, _Imports.razor) and deduplicate them in the merged plan.
⚠ Service conflict check: If both DxReportViewer (native) and DxReportDesigner (JS-based) exist in the same project, check for an IReportProvider + ReportStorageWebExtension conflict before proceeding. See Constraint 16 and 📄 references/resolving-report-names.md.
Step 2 — Draft the Merged Plan
Merge all loaded steps into a single numbered plan ordered by execution sequence. Deduplicate shared setup steps (Program.cs, App.razor, _Imports.razor). Do not present or execute yet.
Step 3A — Anti-Pattern Gate (Mandatory Before Presenting the Plan)
Before presenting the plan to the developer, review every step of the draft against this checklist. If any item is violated, fix the draft first.
Universal checks — apply regardless of scenario:
C3 — Every reporting component page has @rendermode InteractiveServer or @rendermode InteractiveWebAssembly (except Interactive WebAssembly Standalone — no @rendermode needed for entirely client-side apps)
C4 — DxResourceManager.RegisterScripts() is in App.razor <head>, not in a page or component
C5 — Service registration matches the component family and hosting mode (native + Interactive Server → AddDevExpressServerSideBlazorReportViewer; native + WASM Standalone → AddDevExpressWebAssemblyBlazorReportViewer(); JS-based → AddDevExpressBlazorReporting); no cross-family mixing
C6 — report.CreateDocument() is never called synchronously; only await report.CreateDocumentAsync()
C15 — IReportProvider is registered afterAddDevExpressBlazorReporting() / AddDevExpressServerSideBlazorReportViewer() / AddDevExpressWebAssemblyBlazorReportViewer()
C16 — IReportProvider and ReportStorageWebExtension are not both registered in the same DI container
A6 — XtraReport.FromXmlStream() is used everywhere a report is loaded from bytes/stream; LoadLayoutFromXml() does not appear
A7 — DeserializationSettings.RegisterTrustedClass() is called for every custom data source class used in a report, placed beforevar builder = WebApplication.CreateBuilder(args)
A8 — RegisterTrustedAssembly() is not used in place of per-type RegisterTrustedClass()
A11 — No new XtraReport() with no bands is passed to a viewer; a real report class with at least a DetailBand is used
A16 — No component family change was introduced on an existing page unless the user explicitly requested migration
C17 — In multi-project solutions (Interactive WebAssembly Client+Server or Auto), each component is placed and services are registered correctly per Decision Gate D: DxDocumentViewer / DxReportDesigner (component + services in .Client); DxWasmDocumentViewer / DxWasmReportDesigner (component page in .Client, but services in server Program.cs)
C20 — When adding a new component to an existing project, Phase 1 Step 0 (New Component Detection) was applied FIRST — the agent did not assume existing setup patterns
C21 — All required controllers for the new component are implemented per its component family:
Viewers (DxReportViewer, DxDocumentViewer, DxWasmDocumentViewer): only WebDocumentViewerController required
Designers (DxReportDesigner, DxWasmReportDesigner): ALL THREE required: WebDocumentViewerController, ReportDesignerController, QueryBuilderController
If missing: the build succeeds but runtime fails on data source wizard or save/load dialogs
C22 — New component's controller-specific namespace imports are in place:
Viewer only requires: DevExpress.AspNetCore.Reporting.WebDocumentViewer
C23 — If adding a designer to a project with an existing native viewer:
Verify no IReportProvider and ReportStorageWebExtension conflict (see Constraint 16)
Designer requires ReportStorageWebExtension for persistence; if project already has IReportProvider, remove it or replace with storage
C24 — If a new .razor page is being added, a matching <NavLink> entry will be added to the navigation component (see Phase 3 Step 3B) with correct and route name
C26 (NEW) — For components with custom data sources (designer components OR viewers with storage): All data source classes registered with RegisterTrustedClass(), array types registered separately, registrations before CreateBuilder(). See 📄 references/resolving-report-names.md for details.
Scenario-specific checks — also review the ## Antipatterns section of every reference file loaded in Step 1. Fix any violations found there before continuing.
Step 3B — Navigation Link Addition (Only When Adding a New Page)
When to apply: User is adding a new .razor page for a reporting component AND Phase 1 Step 1 item 6 discovered a navigation component.
When to skip:
Modifying an existing viewer/designer page (not adding a new one)
User explicitly requested "don't add navigation" or "no nav link"
Navigation could not be located in Phase 1 (alert user that they must add the link manually)
Tasks:
Locate the navigation component file from Phase 1 Step 1 item 6
Copy one existing <NavLink> element as a template
Create a new <NavLink> entry matching that pattern:
Use the href value matching the @page route from the new .razor file
Adapt the label text to match the component type (e.g., "Report Viewer", "Report Designer", "Document Viewer")
Keep the same CSS classes, icon pattern, and Match attribute as the existing links
Add this step to the plan before "Present and Execute"
During execution (Phase 3 Step 4), add the link to the navigation file after all other changes are complete
Step 3C — Designer Data Source Trust Registration (Only When Designer Has Custom Data Sources)
When to apply:
A designer component (DxReportDesigner / DxWasmReportDesigner) is being added
Phase 2 Decision Gate F found custom data source classes
Data source classes are custom types (not built-in .NET classes)
When to skip:
Designer is not being added
No custom data source classes were found
Reports only use built-in data sources (ObjectDataSource with built-in types, JSON, SQL)
Task: If Decision Gate F identified custom data source classes, add trust registration code to Program.csbeforeWebApplication.CreateBuilder(). This applies when:
Designer components save/load/preview reports with custom data sources
Viewer components open reports from storage containing custom data sources
Resolve component family before writing any code. If the user names the component explicitly, branch immediately using Decision Gates A–C. If the wording is ambiguous, apply the Q2 script.
Existing viewer family is immutable unless migration is explicitly requested. Do not replace DxReportViewer with DxDocumentViewer or vice versa without explicit user consent.
Interactive render mode required. Add @rendermode InteractiveServer or @rendermode InteractiveWebAssembly to every reporting page. Exception: Standalone WASM apps are fully interactive by default — no directive needed.
RegisterScripts in App.razor head.DxResourceManager.RegisterScripts() must be in the <head> element. Without it, no client-side scripts load.
Service registration is family and hosting-mode-specific. Native viewer + Interactive Server: AddDevExpressServerSideBlazorReportViewer(). Native viewer + WASM Standalone: AddDevExpressWebAssemblyBlazorReportViewer(). JS-based: AddDevExpressBlazorReporting(). Do not mix registrations.
CreateDocumentAsync in web. Never call report.CreateDocument() synchronously. Always await report.CreateDocumentAsync().
Skia for non-Windows. Add DevExpress.Drawing.Skia for any Linux/macOS deployment and for WASM viewer or standalone WASM designer.
Version consistency. All DevExpress NuGet packages must use the same version.
Build verification. Run dotnet build --project <path> after changes. Check for errors before reporting success.
Integration is not done until the viewer renders content.new XtraReport() with no bands makes the viewer look broken. Always wire in a real report class before declaring the task done.
IReportProvider registration order. Register after any AddDevExpress*() call — registering before lets the DevExpress proxy override it at runtime.
Multiple designer controllers required.DxReportDesigner/DxWasmReportDesigner need all three: WebDocumentViewerController, ReportDesignerController, QueryBuilderController. Build succeeds if any are missing; runtime fails with 404s. See C21/C22.
IReportProvider and ReportStorageWebExtension are mutually exclusive. Never register both. Designer requires storage; if storage is registered, remove any existing IReportProvider and use XtraReport.FromXmlStream() in the viewer.
Blank viewer → @rendermode InteractiveServer missing, or DxResourceManager.RegisterScripts() not in App.razor <head>
"Service not registered" → wrong AddDevExpress...() call for the component family
404 on API calls (JS-based) → UseDevExpressBlazorReporting() or MapControllers() missing
"Unable to resolve IUrlHelperFactory" → AddControllers() missing before AddDevExpressBlazorReporting()
Designer Save/Load does nothing → ReportStorageWebExtension not registered afterAddDevExpressBlazorReporting()
Reports saved via designer not visible in native viewer → both IReportProvider and ReportStorageWebExtension registered; remove IReportProvider (see Constraint 16)
Component blank or errors immediately in WASM mode → component placed in the wrong project (e.g., DxDocumentViewer in the server project instead of .Client); see Decision Gate D
"Service not registered" in Auto or Interactive WebAssembly (Client + Server) → services registered in .ClientProgram.cs instead of server Program.cs, or vice versa; see Decision Gate D