17 KiB
EnvelopeGenerator — AI Context Reference
Purpose
Digital document signing system with unified Blazor Auto (Server+WASM hybrid) frontend for both Senders and Receivers. Senders create envelopes and place signature fields. Receivers view PDFs, sign documents, export stamped PDFs.
Primary Libraries: DevExpress + PDF.js (PSPDFKit removed)
Migration Notice
EnvelopeGenerator.ReceiverUI ? EnvelopeGenerator.WebUI Migration
The project has been migrated from pure Blazor WebAssembly (ReceiverUI) to Blazor Auto (Server+WASM hybrid) architecture (WebUI) to resolve DevExpress DxPdfViewer compatibility issues.
Reason: DevExpress DxPdfViewer requires backend server-side rendering services that are NOT available in pure WebAssembly projects.
New Structure:
- WebUI (Server project): Hosts server-side components, YARP proxy, DevExpress backend services
- WebUI.Client (WASM project): Client-side components, business logic, services
Migration Details: See MIGRATION_CONTEXT.md
Deployment Architecture
Two Presentation Projects (Both Required):
-
EnvelopeGenerator.API (ASP.NET Core Web API)
- Runs independently (development & production)
- Backend services for document management, authentication, signature endpoints
- Serves as API endpoint for WebUI
-
EnvelopeGenerator.WebUI (Blazor Auto - Server+WASM Hybrid)
- Server Project (
EnvelopeGenerator.WebUI):- YARP Reverse Proxy configured via
yarp.json - Proxies
/api/*requests toAPI:8088 - Hosts server-side components (
@rendermode InteractiveServer) - DevExpress server-side services (DxPdfViewer backend)
- YARP Reverse Proxy configured via
- Client Project (
EnvelopeGenerator.WebUI.Client):- Client-side components (
@rendermode InteractiveWebAssembly) - Business logic services (AuthService, DocumentService, etc.)
- WASM runtime
- Client-side components (
- Server Project (
Request Flow:
Client ? WebUI:XXXX (Blazor Auto)
?? Server-side Pages (DxPdfViewer)
?? Client-side Pages (WASM)
?? YARP Proxy: /api/* ? API:8088
Configuration: EnvelopeGenerator.WebUI/yarp.json
WebUI Route Structure
Root Route
| Route | File | Location | Render Mode |
|---|---|---|---|
/ |
Index.razor |
WebUI.Client/Pages/ |
@rendermode InteractiveWebAssembly |
Sender Routes
| Route | File | Location | Render Mode |
|---|---|---|---|
/sender/login |
LoginSenderPage.razor |
WebUI.Client/Pages/ |
@rendermode InteractiveWebAssembly |
/sender |
EnvelopeSenderPage.razor |
WebUI.Client/Pages/ |
@rendermode InteractiveWebAssembly |
Receiver Routes (PDF Viewers)
| Route | File | Location | Render Mode |
|---|---|---|---|
/envelope/login/{EnvelopeKey} |
LoginReceiverPage.razor |
WebUI.Client/Pages/ |
@rendermode InteractiveWebAssembly |
/envelope/{EnvelopeKey} |
EnvelopeReceiverPage.razor |
WebUI/Components/Pages/ |
@rendermode InteractiveServer |
/envelope/DxPdfViewer |
EnvelopeReceiverPage_DxPdfViewer.razor |
WebUI/Components/Pages/ |
@rendermode InteractiveServer |
/envelope/{EnvelopeKey}/DxReportViewer |
EnvelopeReceiverPage_DxReportViewer.razor |
WebUI/Components/Pages/ |
@rendermode InteractiveServer |
/envelope/Embed |
EnvelopeReceiverPage_embed.razor |
WebUI/Components/Pages/ |
@rendermode InteractiveServer |
Multi-Envelope Support: Receivers can login to multiple envelopes simultaneously (per-envelope cookie authentication).
Render Mode Strategy:
- Client-side Pages (WASM): Login, Sender dashboard, Index (no DevExpress backend required)
- Server-side Pages (Server): PDF viewers (DevExpress DxPdfViewer requires backend)
Architecture Evolution
Old Architecture (Deprecated v1)
- Sender UI:
EnvelopeGenerator.Web(Razor Pages + PSPDFKit) - Receiver UI: Separate project
- Backend:
EnvelopeGenerator.API
Intermediate Architecture (Deprecated v2)
- Unified Frontend:
EnvelopeGenerator.ReceiverUI(Pure Blazor WASM) - Backend:
EnvelopeGenerator.API - Issue: DevExpress
DxPdfViewerdisplayed blank screen (no backend services in WASM)
Current Architecture (Active)
- Frontend:
EnvelopeGenerator.WebUI(Blazor Auto - Server+WASM Hybrid)- WebUI (Server): Server-side components, YARP proxy, DevExpress backend
- WebUI.Client (WASM): Client-side components, services, business logic
- Backend:
EnvelopeGenerator.API - Libraries: DevExpress + PDF.js
- PSPDFKit: REMOVED
Solution Structure
| Project | Target | Purpose |
|---|---|---|
EnvelopeGenerator.API |
net8.0 | ASP.NET Core Web API. Backend for both Senders & Receivers. Auth, PDF serving, signature endpoints. |
EnvelopeGenerator.WebUI |
net8.0 | Blazor Auto Server Project. YARP proxy, server-side components, DevExpress backend services. |
EnvelopeGenerator.WebUI.Client |
net8.0 WASM | Blazor Auto Client Project. Client-side components, services, business logic. |
EnvelopeGenerator.ReceiverUI |
net8.0 WASM | DEPRECATED. Pure Blazor WASM (migrated to WebUI). |
EnvelopeGenerator.Web |
net7/8/9 | DEPRECATED. Legacy Razor Pages (Sender UI). No longer used. |
EnvelopeGenerator.Application |
multi | MediatR CQRS handlers. Business logic. |
EnvelopeGenerator.Domain |
multi | Domain models, constants, interfaces. |
EnvelopeGenerator.Infrastructure |
multi | EF Core repos, DB context. |
EnvelopeGenerator.PdfEditor |
multi | iText7 utilities (NOT used in WebUI). |
EnvelopeGenerator.DependencyInjection |
multi | DI registration helpers. |
| VB.NET projects (Service/Form/BBTests) | net462 | Legacy. Do NOT touch. |
Key Files & Routes
Client-Side Pages (WebUI.Client)
| File | Route | Purpose |
|---|---|---|
WebUI.Client/Pages/Index.razor |
/ |
Application entry point (landing page). |
WebUI.Client/Pages/EnvelopeSenderPage.razor |
/sender |
Sender dashboard (envelope list). |
WebUI.Client/Pages/LoginSenderPage.razor |
/sender/login |
Sender username/password auth. |
WebUI.Client/Pages/LoginReceiverPage.razor |
/envelope/login/{EnvelopeKey} |
Receiver access code auth. |
Server-Side Pages (WebUI)
| File | Route | Purpose |
|---|---|---|
WebUI/Components/Pages/EnvelopeReceiverPage.razor |
/envelope/{key} |
Receiver PDF viewer & signing (PDF.js). |
WebUI/Components/Pages/EnvelopeReceiverPage_DxPdfViewer.razor |
/envelope/DxPdfViewer |
DevExpress PDF Viewer (test page). |
WebUI/Components/Pages/EnvelopeReceiverPage_DxReportViewer.razor |
/envelope/{key}/DxReportViewer |
DevExpress Report Viewer. |
WebUI/Components/Pages/EnvelopeReceiverPage_embed.razor |
/envelope/Embed |
Embedded PDF viewer (iframe). |
Services & Assets
| File | Purpose |
|---|---|
WebUI.Client/Services/AuthService.cs |
Receiver + Sender authentication. |
WebUI.Client/Services/SignatureCacheService.cs |
Signature caching (Redis/SQL). |
WebUI.Client/Services/DocumentService.cs |
PDF document retrieval. |
WebUI/wwwroot/js/pdf-viewer.js |
PDF.js wrapper (zoom, pagination, thumbnails). |
WebUI/wwwroot/js/receiver-signature.js |
Signature pad (draw/type/image). |
WebUI/wwwroot/css/envelope-viewer.css |
EnvelopeViewer styles. |
API/Controllers/CacheController.cs |
Signature cache endpoints. |
Coordinate System — CRITICAL
Database Format: INCHES (GdPicture14 native)
Origin: Top-left corner
Axes: X right, Y down
Conversion Formulas
| From INCHES to | Formula | Example |
|---|---|---|
| DevExpress DX | x_DX = x_inches * 100 |
1.5" ? 150 DX |
| PDF Points | x_pt = x_inches * 72 |
1.5" ? 108 pt |
| PDF.js Pixels | Normalize ? scale | (x_inches / pageWidth) * canvasWidth * scale |
A4 Dimensions:
- Width: 8.27" = 595pt = 827 DX
- Height: 11.69" = 842pt = 1169 DX
Unit Systems
| System | Unit | Origin | Y-Axis |
|---|---|---|---|
| Database (GdPicture14) | Inches | Top-left | Down |
| PDF.js | Pixels | Top-left | Down |
| iText7 PDF | Points (1/72") | Bottom-left | Up (flip required) |
| REMOVED |
EnvelopeReceiver — PDF.js Viewer & Signing
Route: /envelope/{EnvelopeKey}
Tech: PDF.js 3.11.174 + Blazor Server (@rendermode InteractiveServer) + configurable quality
File: WebUI/Components/Pages/EnvelopeReceiverPage.razor
Key Features
- HiDPI/Retina support (4x quality)
- Configurable quality (
appsettings.json) - Unlimited zoom (50%-300%)
- Ctrl+Wheel global zoom
- Resizable thumbnail sidebar (150-400px, localStorage)
- Responsive (desktop/mobile)
Configuration
File: WebUI/wwwroot/appsettings.json
{
"PdfViewer": {
"ThumbnailBaseScale": 0.75,
"ThumbnailEnableHiDPI": true,
"MainCanvasEnableHiDPI": true,
"ZoomStepPercentage": 5
}
}
JavaScript API
File: WebUI/wwwroot/js/pdf-viewer.js
window.pdfViewer = {
initialize(canvasId, pdfDataUrl, dotNetRef),
renderPage(num),
renderSignatureButtons(signatures, pageNum, dotNetRef),
applySignature(signatureId, dataUrl, fullName, position, place),
zoomIn(), zoomOut(), dispose()
}
Signature Workflow — EnvelopeReceiver
IMPORTANT: iText7 NOT used (GPL license issue). Client-side overlay system only.
Workflow Steps
-
Page Load:
- Check
SignatureCacheServicefor cached signature - If cached ? skip popup, load signature
- If not ? show automatic popup (mandatory)
- Check
-
Signature Popup (DxPopup):
- Cannot close (no X, no ESC, no outside-click)
- 3 Tabs: Draw (canvas) / Text (font select) / Image (upload)
- Required: Full name, Place
- Optional: Position
- Save ? Store in
_capturedSignature, cache via API
-
Signature Buttons:
- Render purple "Unterschreiben" buttons at signature field positions
- Coordinates: INCHES ? POINTS ? Pixels (scaled)
- File:
pdf-viewer.js?renderSignatureButtons()
-
Apply Signature (Click "Unterschreiben"):
- JS: Remove button, create HTML overlay
- Format: Image + separator + text (Name, Position, Place, Date)
- NOT stamped on PDF bytes (visual overlay only)
-
Re-rendering:
- Zoom/Page change ? recalculate button positions
- Session state:
_capturedSignature(lost on refresh)
Data Model
File: WebUI.Client/Models/SignatureCaptureDto.cs
public sealed record SignatureCaptureDto {
public required string DataUrl { get; init; } // base64 PNG
public required string FullName { get; init; }
public string Position { get; init; } = ""; // Optional
public required string Place { get; init; }
}
Signature Caching
Purpose: Persist signature across page refreshes (distributed cache: Redis/SQL)
API Endpoints
Controller: API/Controllers/CacheController.cs
POST /api/Cache/SignatureCapture/{envelopeKey}— SaveGET /api/Cache/SignatureCapture/{envelopeKey}— LoadDELETE /api/Cache/SignatureCapture/{envelopeKey}— Delete
Cache Key Format:
signature:91751687-8ae6-4777-bf5f-b8846085e62e:{envelopeKey}
Configuration: appsettings.json
{
"Cache": {
"SignatureCacheExpiration": null // or "02:00:00" for 2h
}
}
Service
File: WebUI.Client/Services/SignatureCacheService.cs
public class SignatureCacheService {
Task SaveSignatureAsync(string envelopeKey, SignatureCaptureDto signature);
Task<SignatureCaptureDto?> GetSignatureAsync(string envelopeKey);
Task DeleteSignatureAsync(string envelopeKey);
}
Error Handling: Fire-and-forget saves, graceful degradation on load failure.
Sender Login
Route: /sender/login
File: WebUI.Client/Pages/LoginSenderPage.razor
Tech: Bootstrap 5 + DevExpress Blazing Berry theme
AuthService Extension
File: WebUI.Client/Services/AuthService.cs
public enum SenderLoginResult { Success, InvalidCredentials, Error }
public async Task<SenderLoginResult> LoginSenderAsync(string username, string password) {
var response = await http.PostAsJsonAsync(
$"{_api.BaseUrl}/api/auth?cookie=true",
new { username, password });
return response.StatusCode switch {
HttpStatusCode.OK => SenderLoginResult.Success,
HttpStatusCode.Unauthorized => SenderLoginResult.InvalidCredentials,
_ => SenderLoginResult.Error
};
}
API Integration
Endpoint: POST /api/auth?cookie=true
Request:
{ "username": "TekH", "password": "***" }
Response:
200 OK? Cookie set, redirect to/sender401 Unauthorized? Show error: "Ungültige Anmeldedaten"- Other ? Show error: "Serverfehler"
Cookie: HTTP-only, Secure (HTTPS), SameSite=Strict
UI Flow
- User enters username + password
- Click "Anmelden" or press Enter
- Call
AuthService.LoginSenderAsync() - Success ?
Navigation.NavigateTo("/sender", forceLoad: true) - Error ? Display alert
Receiver Login
Route: /envelope/login/{EnvelopeKey}
File: WebUI.Client/Pages/LoginReceiverPage.razor
Multi-Envelope Support: Cookies are stored per-envelope (e.g., AuthTokenSignFLOWReceiver.{envelopeKey}), allowing simultaneous authentication for multiple envelopes in the same browser session.
AuthService Method
public enum EnvelopeLoginResult { Success, InvalidCode, NotFound, Error }
public async Task<EnvelopeLoginResult> LoginEnvelopeReceiverAsync(string key, string accessCode) {
var form = new MultipartFormDataContent();
form.Add(new StringContent(accessCode), "AccessCode");
var response = await http.PostAsync(
$"{_api.BaseUrl}/api/Auth/envelope-receiver/{Uri.EscapeDataString(key)}", form);
return response.StatusCode switch {
HttpStatusCode.OK => EnvelopeLoginResult.Success,
HttpStatusCode.Unauthorized => EnvelopeLoginResult.InvalidCode,
HttpStatusCode.NotFound => EnvelopeLoginResult.NotFound,
_ => EnvelopeLoginResult.Error
};
}
Success: Redirect to /envelope/{key}
NuGet Packages (WebUI.Client)
| Package | Version | Purpose |
|---|---|---|
DevExpress.Blazor.* |
25.2.3 | UI components (grids, popups, etc.) |
SkiaSharp.* |
3.119.1 | WASM rendering |
itext |
NOT USED (GPL license) |
External CDN:
- PDF.js 3.11.174:
https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js
Mistakes History — Do NOT Repeat
| Mistake | Why Wrong |
|---|---|
| Using iText7 in EnvelopeReceiver | GPL license issue. Use overlay system instead. |
| Using PSPDFKit | Removed from architecture. Use PDF.js + DevExpress. |
| Hardcoded quality values in PDF.js | Use appsettings.json for configurability. |
| Complex toolbar layouts | User wants simplicity. Keep horizontal layout. |
| Over-designed UI (gradients/badges) | User prefers simple text labels. |
| Ignoring "revert" instructions | Revert HTML structure, not just CSS. |
BottomMarginBand for signatures |
Repeats on every page. Use DetailBand. |
imageY = (page-1) * 1169 + ann.Y |
Inflates DetailBand. Calculate per-page. |
Development Notes
Deprecated Projects
DO NOT USE:
EnvelopeGenerator.ReceiverUI(Pure Blazor WASM) — Migrated to WebUI (DevExpress compatibility issue)EnvelopeGenerator.Web(Razor Pages) — Replaced by unified WebUI- PSPDFKit — Removed, use PDF.js + DevExpress instead
Legacy Projects (VB.NET)
DO NOT TOUCH: EnvelopeGenerator.Service, EnvelopeGenerator.Form, EnvelopeGenerator.BBTests
Signature Coordinate Evidence
File: EnvelopeGenerator.Form/frmFieldEditor.vb (VB.NET)
Private Const SIGNATURE_WIDTH As Single = 1.77 ' inches
Private Const SIGNATURE_HEIGHT As Single = 1.96 ' inches
Sub LoadAnnotation(pElement As Signature, ...)
oAnnotation.Left = CSng(pElement.X) ' Direct INCHES assignment
oAnnotation.Top = CSng(pElement.Y)
End Sub
Proves database uses INCHES natively.
Quick Reference
When working with coordinates:
- Database ? UI: INCHES × 72 = PDF Points
- UI ? Display: Points × scale = Pixels
- iText7 stamping: Flip Y-axis (top-down ? bottom-up)
When adding features:
- Check
Mistakes Historyfirst - Prefer simplicity over complexity
- Use
appsettings.jsonfor configuration - Keep consistent with existing design (Bootstrap 5 + Blazing Berry)
- Unified frontend: WebUI serves both Senders and Receivers
- Render mode: Client-side (WASM) for login/dashboard, Server-side for PDF viewers
When debugging:
- Coordinates: Always check unit system (inches/points/pixels)
- Authentication: Check cookie name/domain/SameSite
- Cache: Check Redis/SQL connection + key format
- Frontend confusion: Only use WebUI (ReceiverUI/Web are deprecated)
- Blank DxPdfViewer: Ensure page has
@rendermode InteractiveServer
Last Updated: 2025-01-27 (ReceiverUI ? WebUI migration complete)