Transitioned PDF rendering in `EnvelopeReceiverPage.razor` from `PDF.js` to `DevExpress DxPdfViewer`. Updated code and documentation to reflect the verified API of `DevExpress.Blazor.PdfViewer` v25.2.3, addressing its limitations (e.g., lack of `GoToPageAsync`, `PageNumberChanged`). Implemented `CustomizeToolbar` for navigation/zoom controls and manual state tracking for `_currentPage` and `_viewerZoomLevel`. Replaced JavaScript interop for page count with the `PageCount` property. Retained the custom thumbnail sidebar due to API constraints. Added temporary debug tools for DOM analysis and navigation testing. Updated `TESTING_CHECKLIST.md` and added `DEVEXPRESS_V25_LIMITATIONS.md` to document the new strategy, API limitations, and testing scenarios. Cross-page signature navigation implemented with state updates, though visible page changes remain manual. Prepared for future improvements while ensuring functional migration to `DxPdfViewer`.
15 KiB
EnvelopeGenerator — Current Workspace Context
Purpose
Digital document signing system for senders and receivers.
- Senders authenticate, view envelope lists, and manage envelope workflows.
- Receivers authenticate per envelope, open PDFs, create signatures, and apply them in the viewer.
- The active UI stack is
Blazor Autowith server-side and WebAssembly render modes. - Primary UI/PDF libraries are
DevExpressandPDF.js.
Active Application Structure
Main Host
Primary active application: EnvelopeGenerator.Server
EnvelopeGenerator.Server is the current runtime host and contains:
- Blazor server host
- WebAssembly host integration
- API controllers
- authentication/authorization setup
- Swagger/Scalar setup
- YARP reverse proxy configuration
- DevExpress server-side services
- SQL Server distributed cache setup
Client Project
Client UI project: EnvelopeGenerator.Server.Client
This project contains:
- WebAssembly-rendered pages
- client-side services
- client models and options
- sender and receiver login flows
Other Projects
EnvelopeGenerator.Application— MediatR/CQRS handlers and business logicEnvelopeGenerator.Domain— domain models, constants, shared abstractionsEnvelopeGenerator.Infrastructure— EF Core and infrastructure servicesEnvelopeGenerator.PdfEditor— PDF-related backend utilitiesEnvelopeGenerator.API— still exists in the solution, but the current merged app host isEnvelopeGenerator.Server
Legacy / Do Not Touch
EnvelopeGenerator.ServiceEnvelopeGenerator.FormEnvelopeGenerator.BBTestsEnvelopeGenerator.CommonServices
Current Hosting Model
EnvelopeGenerator.Server/Program.cs currently configures:
AddRazorComponents()with both interactive server and interactive WebAssembly componentsAddControllers()andMapControllers()- JWT authentication for sender and receiver flows
- cookie authentication
- authorization policies using
AuthScheme.Sender,AuthScheme.Receiver,AuthPolicy.Sender,AuthPolicy.Receiver AddReverseProxy()withyarp.json- Swagger / OpenAPI / Scalar
- distributed SQL Server cache
- DevExpress Blazor and DevExpress PDF Viewer server-side services
- request localization middleware
This means the active app is a merged UI + API host.
Reverse Proxy
Config file: EnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json
Current YARP usage is focused on AuthHub forwarding, not a general /api/* -> EnvelopeGenerator.API proxy.
Configured routes forward:
POST /api/auth-> AuthHub/api/auth/sign-flowPOST /api/Auth/envelope-receiver/{key}-> AuthHub/api/auth/envelope-receiver/{key}?cookie=true
Active Routes and Files
WebAssembly Pages (EnvelopeGenerator.Server.Client)
| Route | File | Render Mode | Purpose |
|---|---|---|---|
/ |
EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/IndexPage.razor |
WebAssembly | Landing page |
/sender/login |
EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/LoginSenderPage.razor |
WebAssembly | Sender login |
/sender |
EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor |
WebAssembly (prerender: false) |
Sender dashboard |
/envelope/login/{EnvelopeKey} |
EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/LoginReceiverPage.razor |
WebAssembly | Receiver login |
Server Pages (EnvelopeGenerator.Server)
| Route | File | Render Mode | Purpose |
|---|---|---|---|
/envelope/{EnvelopeKey} |
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor |
InteractiveServer | Main receiver PDF viewer and signing page |
/envelope/DxPdfViewer |
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor |
InteractiveServer | DevExpress PDF Viewer test page |
/envelope/{EnvelopeKey}/DxReportViewer |
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_DxReportViewer.razor |
InteractiveServer | DevExpress report-based PDF rendering |
/envelope/Embed |
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage_embed.razor |
InteractiveServer | Embedded browser PDF view test page |
Current API Location
The active application exposes controllers from:
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers
Current controller set includes:
AnnotationControllerAuthControllerCacheControllerConfigControllerDocumentControllerEmailTemplateControllerEnvelopeControllerEnvelopeReceiverControllerEnvelopeTypeControllerHistoryControllerLocalizationControllerReadOnlyControllerReceiverControllerSignatureControllerTfaRegistrationController
Do not assume API behavior lives only in EnvelopeGenerator.API; the active merged host contains controller endpoints directly.
Authentication Model
Sender
Client login page uses EnvelopeGenerator.Server.Client/Services/AuthService.cs.
Key sender endpoints:
POST /api/auth?cookie=true— loginGET /api/auth/check— current sender access checkPOST /api/auth/logout— logout
Receiver
Receiver authentication is per envelope.
Key receiver endpoints used by client services:
POST /api/Auth/envelope-receiver/{envelopeKey}— submit access codeGET /api/auth/check/envelope/{envelopeKey}— check accessPOST /api/auth/logout/envelope/{envelopeKey}— logout receiver for one envelope
Receiver cookie resolution in server auth uses an envelope-specific cookie name derived from:
AuthTokenSignFLOWReceiver.{envelopeKey}pattern
Receiver Server-Side Authorization
EnvelopeReceiverPage.razor does not rely on its own API access-check call for page authorization.
It uses:
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs
Behavior:
- tries the current
HttpContext.User - if needed, reads the per-envelope receiver cookie directly
- validates the JWT with the receiver auth scheme
- verifies the token subject matches the route envelope key
Receiver Page Data Loading
Main server-side page data service:
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs
This service loads directly via MediatR and distributed cache:
- document bytes
- receiver envelope data
- signature placeholders
- cached signature data
For signature placeholders, the service:
- reads document receiver elements
- filters them for the authenticated receiver
- converts coordinates to
UnitOfLength.Pointbefore UI use
Receiver PDF Viewer
Main file: EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/EnvelopeReceiverPage.razor
Current receiver viewer characteristics:
- route:
/envelope/{EnvelopeKey} - render mode:
InteractiveServer - PDF rendering: Migration in progress from
PDF.jstoDxPdfViewer - toolbar: page navigation, zoom, thumbnail toggle, signature navigation, signature reset
- signature popup:
DxPopup - thumbnail sidebar: resizable and stored in
localStorage
⚠️ CRITICAL: DevExpress DxPdfViewer Control Requirements
Verified API for installed DevExpress.Blazor.PdfViewer v25.2.3:
| Property | Access | Notes |
|---|---|---|
DocumentContent |
[Parameter] GET/SET |
Feed PDF as byte[] |
ZoomLevel |
[Parameter] GET/SET |
Factor (not percentage): 1.5 = 150% |
IsSinglePagePreview |
[Parameter] GET/SET |
Single page mode |
CssClass |
[Parameter] GET/SET |
CSS class |
DocumentName |
[Parameter] GET/SET |
Download filename |
SizeMode |
[Parameter] GET/SET |
Small / Medium / Large |
PageCount |
Read-only GET | Total pages — no JS call needed |
ActivePageIndex |
Read-only GET | Current page (0-based) — cannot SET |
CustomizeToolbar |
Event | Only available toolbar event |
Does NOT exist in v25.2.3 — do NOT use:
GoToPageAsync()❌GoToNextPageAsync()❌ZoomAsync()❌PageNumberChangedevent ❌ZoomLevelChangedevent ❌ToolbarVisibleproperty ❌
Correct approach:
<DxPdfViewer @ref="_pdfViewer"
DocumentContent="@_pdfDocumentContent"
ZoomLevel="@_viewerZoomLevel"
IsSinglePagePreview="true"
CustomizeToolbar="OnCustomizeToolbar" />
// ZoomLevel: always divide by 100 (factor, not percentage)
_viewerZoomLevel = _currentZoom / 100d; // 150 -> 1.5
// PageCount: read directly, no JS needed
_totalPages = _pdfViewer.PageCount;
// Page navigation: only via CustomizeToolbar buttons
protected void OnCustomizeToolbar(ToolbarModel toolbarModel)
{
toolbarModel.AllItems.Clear();
var nextButton = new ToolbarItem
{
IconCssClass = "dx-icon-chevronnext",
Enabled = _currentPage < _totalPages,
Click = async (args) =>
{
_currentPage++;
_viewerZoomLevel = _currentZoom / 100d;
await InvokeAsync(StateHasChanged);
await RenderSignatureButtonsAsync();
}
};
toolbarModel.AllItems.Add(nextButton);
}
JavaScript role after migration:
- Overlay geometry calculations only
- Thumbnail rendering via PDF.js helper
- Custom UI interactions (sidebar resize, signature canvas)
- NOT for controlling DxPdfViewer page or zoom
See DEVEXPRESS_V25_LIMITATIONS.md for complete verified API reference.
JS Assets
EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/pdf-viewer.jsEnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/js/receiver-signature.js
CSS
EnvelopeGenerator.Server/EnvelopeGenerator.Server/wwwroot/css/envelope-viewer.css
PDF.js CDN
https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.jshttps://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css
Signature Workflow
Receiver signatures are handled as a viewer overlay workflow.
Current behavior
- Server-side authorization validates receiver access.
- The page loads document bytes, receiver data, signature placeholders, and cached signature state.
- If no cached signature exists, the signature popup opens automatically.
- Receiver creates signature using one of three tabs:
- draw
- text
- image
- Required metadata:
- full name
- place
- Optional metadata:
- position
- Clicking a signature placeholder applies the signature as a client-side overlay in the PDF viewer.
Important note
Although itext is referenced by the server project, the current receiver page signing flow is not PDF stamping-based. The active receiver UI uses client-side overlay behavior in the viewer.
Signature DTO
EnvelopeGenerator.Server.Client/Models/SignatureCaptureDto.cs
public sealed record SignatureCaptureDto {
public required string DataUrl { get; init; }
public required string FullName { get; init; }
public string Position { get; init; } = "";
public required string Place { get; init; }
}
Signature Cache
Active cache model
The current receiver page cache flow is handled directly in the server project through:
EnvelopeReceiverPageDataServiceIDistributedCache- SQL Server distributed cache configuration from
Program.cs
Cache key format
Current server-side key prefix:
envelope-generator.receiver-ui.signature:{receiverSignature}
This is different from an envelope-key-only cache convention.
Config
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Options/CacheOptions.cs
- section name:
Cache - option:
SignatureCacheExpiration
Related controller
A cache API controller also exists in:
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/CacheController.cs
Sender Dashboard
Main file: EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Pages/EnvelopeSenderPage.razor
Current behavior:
- checks sender access through
AuthService.CheckSenderAccessAsync() - redirects to
/sender/loginwhen unauthorized - loads envelope list through client
EnvelopeService - separates envelopes into active/completed tabs
- uses
DevExpress DxGrid
The sender page is active, but create/edit/delete actions are still marked with TODO behavior in the UI page.
Localization
Current server host localization setup in Program.cs:
- supported cultures:
de-DE,en-US - request localization middleware is enabled
QueryStringRequestCultureProvideris added- cookie-based localization services are registered via
AddCookieBasedLocalizer()
Do not assume the old ReceiverUI-only localStorage culture approach is the current source of truth for the active host.
Coordinate System
Source data
Database signature coordinates are still based on:
- unit: inches
- origin: top-left
- axes: X right, Y down
Relevant conversions
- inches -> PDF points:
x_pt = x_inches * 72 - inches -> DevExpress DX units:
x_dx = x_inches * 100
Current receiver page behavior
The server page data service converts signature placeholders to points before sending them into the viewer workflow.
Unit systems to keep in mind
| System | Unit | Origin | Y-axis |
|---|---|---|---|
| Database | Inches | Top-left | Down |
| PDF.js display | Pixels | Top-left | Down |
| PDF points | Points | Depends on PDF model | Depends on consumer |
| DevExpress DX | 1/100 inch style coordinates | Top-left-oriented usage in this app | Down-oriented usage |
Key Services and Files
Client services
EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/AuthService.csEnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.csEnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocumentService.csEnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureCacheService.cs
Server services
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeAuthService.csEnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/IEnvelopeAuthService.csEnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.csEnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs
Server config and host files
EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.csEnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json
Working Rules for This Workspace
- Treat
EnvelopeGenerator.Serveras the active main application host. - Treat
EnvelopeGenerator.Server.Clientas the active client UI project. - Prefer current
Server/Server.Clientpaths over oldWebUI/ReceiverUIreferences. - Do not use
EnvelopeGenerator.WeborEnvelopeGenerator.ReceiverUIas the primary implementation target unless explicitly asked. - Do not modify the legacy VB.NET projects unless explicitly requested.
- For receiver PDF/signature work, prefer the current
PDF.js-based flow inEnvelopeReceiverPage.razor. - For DevExpress PDF viewer issues, remember server-side services are registered in
EnvelopeGenerator.Server.
Last Updated: 2026-06-29