Renamed Razor component files in ReceiverUI to follow a consistent "Page" naming convention. Updated routes to reference the renamed files. Introduced the root route (`/`) with `Index.razor` as the application entry point. Updated documentation to reflect the file renames, route changes, and multi-envelope support. Clarified Redis/SQL caching details and deprecated the "Web" frontend in favor of `ReceiverUI`. These changes improve maintainability, consistency, and developer experience.
14 KiB
EnvelopeGenerator — AI Context Reference
Purpose
Digital document signing system with unified Blazor WASM 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)
Deployment Architecture
Two Presentation Projects (Both Required):
-
EnvelopeGenerator.API (ASP.NET Core Web API)
- Runs independently (development & production)
- YARP Reverse Proxy configured via
yarp.json - Proxies requests to:
EnvelopeGenerator.ReceiverUI(Blazor WASM)- External Auth.API service
- Serves as single entry point for all requests
-
EnvelopeGenerator.ReceiverUI (Blazor WebAssembly)
- Runs on separate host/port
- Accessed only through API proxy (not directly)
- Serves static files (HTML, JS, CSS, WASM)
Request Flow:
Client ? API:8088 (YARP Proxy) ? ReceiverUI:52936 (Blazor WASM)
? Auth.API:9090 (External Auth Service)
Configuration: EnvelopeGenerator.API/yarp.json
ReceiverUI Route Structure
Root Route
| Route | File | Purpose |
|---|---|---|
/ |
Index.razor |
Application entry point (landing page). |
Sender Routes
| Route | File | Purpose |
|---|---|---|
/sender/login |
LoginSenderPage.razor |
Username/password authentication |
/sender |
EnvelopeSenderPage.razor |
Sender dashboard (envelope list) |
Receiver Routes
| Route | File | Purpose |
|---|---|---|
/envelope/login/{EnvelopeKey} |
LoginReceiverPage.razor |
Access code authentication for specific envelope |
/envelope/{EnvelopeKey} |
EnvelopeReceiverPage.razor |
View & sign envelope (PDF.js viewer) |
Multi-Envelope Support: Receivers can login to multiple envelopes simultaneously (per-envelope cookie authentication).
Architecture Evolution
Old Architecture (Deprecated)
- Sender UI:
EnvelopeGenerator.Web(Razor Pages + PSPDFKit) - Receiver UI:
EnvelopeGenerator.ReceiverUI(Blazor WASM + PDF.js) - Backend:
EnvelopeGenerator.API
Current Architecture
- Unified Frontend:
EnvelopeGenerator.ReceiverUI(Blazor WASM) — Both Senders & Receivers - Backend:
EnvelopeGenerator.API— Both Senders & Receivers - 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.ReceiverUI |
net8.0 WASM | Unified Blazor WebAssembly Frontend. UI for both Senders & Receivers. YARP proxy to API. |
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 ReceiverUI). |
EnvelopeGenerator.DependencyInjection |
multi | DI registration helpers. |
| VB.NET projects (Service/Form/BBTests) | net462 | Legacy. Do NOT touch. |
Key Files & Routes
| File | Route/Purpose |
|---|---|
ReceiverUI/Pages/Index.razor |
/ — Application entry point (landing page). |
ReceiverUI/Pages/EnvelopeSenderPage.razor |
/sender — Sender dashboard (envelope list). |
ReceiverUI/Pages/EnvelopeReceiverPage.razor |
/envelope/{key} — Receiver PDF viewer & signing. |
ReceiverUI/Pages/LoginSenderPage.razor |
/sender/login — Sender username/password auth. |
ReceiverUI/Pages/LoginReceiverPage.razor |
/envelope/login/{EnvelopeKey} — Receiver access code auth. |
ReceiverUI/wwwroot/js/pdf-viewer.js |
PDF.js wrapper (zoom, pagination, thumbnails). |
ReceiverUI/wwwroot/js/receiver-signature.js |
Signature pad (draw/type/image). |
ReceiverUI/wwwroot/css/envelope-viewer.css |
EnvelopeViewer styles. |
ReceiverUI/Services/AuthService.cs |
Receiver + Sender authentication. |
ReceiverUI/Services/SignatureCacheService.cs |
Signature caching (Redis/SQL). |
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 WASM + configurable quality
File: ReceiverUI/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: ReceiverUI/wwwroot/appsettings.json
{
"PdfViewer": {
"ThumbnailBaseScale": 0.75,
"ThumbnailEnableHiDPI": true,
"MainCanvasEnableHiDPI": true,
"ZoomStepPercentage": 5
}
}
JavaScript API
File: ReceiverUI/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: ReceiverUI/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: ReceiverUI/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: ReceiverUI/Pages/LoginSenderPage.razor
Tech: Bootstrap 5 + DevExpress Blazing Berry theme
AuthService Extension
File: ReceiverUI/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: ReceiverUI/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 (ReceiverUI)
| 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.Web(Razor Pages) — Replaced by unified ReceiverUI- 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: ReceiverUI serves both Senders and Receivers
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 ReceiverUI (Web is deprecated)
Last Updated: Session 19 (Razor file naming convention + Index route proxy)