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`.
420 lines
15 KiB
Markdown
420 lines
15 KiB
Markdown
# 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 Auto` with server-side and WebAssembly render modes.
|
|
- Primary UI/PDF libraries are `DevExpress` and `PDF.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 logic
|
|
- `EnvelopeGenerator.Domain` — domain models, constants, shared abstractions
|
|
- `EnvelopeGenerator.Infrastructure` — EF Core and infrastructure services
|
|
- `EnvelopeGenerator.PdfEditor` — PDF-related backend utilities
|
|
- `EnvelopeGenerator.API` — still exists in the solution, but the current merged app host is `EnvelopeGenerator.Server`
|
|
|
|
### Legacy / Do Not Touch
|
|
- `EnvelopeGenerator.Service`
|
|
- `EnvelopeGenerator.Form`
|
|
- `EnvelopeGenerator.BBTests`
|
|
- `EnvelopeGenerator.CommonServices`
|
|
|
|
---
|
|
|
|
## Current Hosting Model
|
|
|
|
`EnvelopeGenerator.Server/Program.cs` currently configures:
|
|
- `AddRazorComponents()` with both interactive server and interactive WebAssembly components
|
|
- `AddControllers()` and `MapControllers()`
|
|
- JWT authentication for sender and receiver flows
|
|
- cookie authentication
|
|
- authorization policies using `AuthScheme.Sender`, `AuthScheme.Receiver`, `AuthPolicy.Sender`, `AuthPolicy.Receiver`
|
|
- `AddReverseProxy()` with `yarp.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-flow`
|
|
- `POST /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:
|
|
- `AnnotationController`
|
|
- `AuthController`
|
|
- `CacheController`
|
|
- `ConfigController`
|
|
- `DocumentController`
|
|
- `EmailTemplateController`
|
|
- `EnvelopeController`
|
|
- `EnvelopeReceiverController`
|
|
- `EnvelopeTypeController`
|
|
- `HistoryController`
|
|
- `LocalizationController`
|
|
- `ReadOnlyController`
|
|
- `ReceiverController`
|
|
- `SignatureController`
|
|
- `TfaRegistrationController`
|
|
|
|
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` — login
|
|
- `GET /api/auth/check` — current sender access check
|
|
- `POST /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 code
|
|
- `GET /api/auth/check/envelope/{envelopeKey}` — check access
|
|
- `POST /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.Point` before 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.js` to `DxPdfViewer`**
|
|
- 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()` ❌
|
|
- `PageNumberChanged` event ❌
|
|
- `ZoomLevelChanged` event ❌
|
|
- `ToolbarVisible` property ❌
|
|
|
|
**Correct approach:**
|
|
```razor
|
|
<DxPdfViewer @ref="_pdfViewer"
|
|
DocumentContent="@_pdfDocumentContent"
|
|
ZoomLevel="@_viewerZoomLevel"
|
|
IsSinglePagePreview="true"
|
|
CustomizeToolbar="OnCustomizeToolbar" />
|
|
```
|
|
|
|
```csharp
|
|
// 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.js`
|
|
- `EnvelopeGenerator.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.js`
|
|
- `https://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
|
|
1. Server-side authorization validates receiver access.
|
|
2. The page loads document bytes, receiver data, signature placeholders, and cached signature state.
|
|
3. If no cached signature exists, the signature popup opens automatically.
|
|
4. Receiver creates signature using one of three tabs:
|
|
- draw
|
|
- text
|
|
- image
|
|
5. Required metadata:
|
|
- full name
|
|
- place
|
|
6. Optional metadata:
|
|
- position
|
|
7. 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`
|
|
|
|
```csharp
|
|
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:
|
|
- `EnvelopeReceiverPageDataService`
|
|
- `IDistributedCache`
|
|
- 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/login` when 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
|
|
- `QueryStringRequestCultureProvider` is 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.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/EnvelopeService.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/DocumentService.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureCacheService.cs`
|
|
|
|
### Server services
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeAuthService.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/IEnvelopeAuthService.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs`
|
|
|
|
### Server config and host files
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/Program.cs`
|
|
- `EnvelopeGenerator.Server/EnvelopeGenerator.Server/yarp.json`
|
|
|
|
---
|
|
|
|
## Working Rules for This Workspace
|
|
|
|
- Treat `EnvelopeGenerator.Server` as the active main application host.
|
|
- Treat `EnvelopeGenerator.Server.Client` as the active client UI project.
|
|
- Prefer current `Server` / `Server.Client` paths over old `WebUI` / `ReceiverUI` references.
|
|
- Do not use `EnvelopeGenerator.Web` or `EnvelopeGenerator.ReceiverUI` as 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 in `EnvelopeReceiverPage.razor`.
|
|
- For DevExpress PDF viewer issues, remember server-side services are registered in `EnvelopeGenerator.Server`.
|
|
|
|
---
|
|
|
|
**Last Updated:** 2026-06-29
|