Files
EnvelopeGenerator/COPILOT_CONTEXT.md

17 KiB
Raw Blame History

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):

  1. 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
  2. EnvelopeGenerator.WebUI (Blazor Auto - Server+WASM Hybrid)

    • Server Project (EnvelopeGenerator.WebUI):
      • YARP Reverse Proxy configured via yarp.json
      • Proxies /api/* requests to API:8088
      • Hosts server-side components (@rendermode InteractiveServer)
      • DevExpress server-side services (DxPdfViewer backend)
    • Client Project (EnvelopeGenerator.WebUI.Client):
      • Client-side components (@rendermode InteractiveWebAssembly)
      • Business logic services (AuthService, DocumentService, etc.)
      • WASM runtime

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 DxPdfViewer displayed 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)
PSPDFKit Points Top-left 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

  1. HiDPI/Retina support (4x quality)
  2. Configurable quality (appsettings.json)
  3. Unlimited zoom (50%-300%)
  4. Ctrl+Wheel global zoom
  5. Resizable thumbnail sidebar (150-400px, localStorage)
  6. 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

  1. Page Load:

    • Check SignatureCacheService for cached signature
    • If cached ? skip popup, load signature
    • If not ? show automatic popup (mandatory)
  2. 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
  3. Signature Buttons:

    • Render purple "Unterschreiben" buttons at signature field positions
    • Coordinates: INCHES ? POINTS ? Pixels (scaled)
    • File: pdf-viewer.js ? renderSignatureButtons()
  4. 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)
  5. 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} — Save
  • GET /api/Cache/SignatureCapture/{envelopeKey} — Load
  • DELETE /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 /sender
  • 401 Unauthorized ? Show error: "Ungültige Anmeldedaten"
  • Other ? Show error: "Serverfehler"

Cookie: HTTP-only, Secure (HTTPS), SameSite=Strict

UI Flow

  1. User enters username + password
  2. Click "Anmelden" or press Enter
  3. Call AuthService.LoginSenderAsync()
  4. Success ? Navigation.NavigateTo("/sender", forceLoad: true)
  5. 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 8.0.5 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:

  1. Database ? UI: INCHES × 72 = PDF Points
  2. UI ? Display: Points × scale = Pixels
  3. iText7 stamping: Flip Y-axis (top-down ? bottom-up)

When adding features:

  1. Check Mistakes History first
  2. Prefer simplicity over complexity
  3. Use appsettings.json for configuration
  4. Keep consistent with existing design (Bootstrap 5 + Blazing Berry)
  5. Unified frontend: WebUI serves both Senders and Receivers
  6. Render mode: Client-side (WASM) for login/dashboard, Server-side for PDF viewers

When debugging:

  1. Coordinates: Always check unit system (inches/points/pixels)
  2. Authentication: Check cookie name/domain/SameSite
  3. Cache: Check Redis/SQL connection + key format
  4. Frontend confusion: Only use WebUI (ReceiverUI/Web are deprecated)
  5. Blank DxPdfViewer: Ensure page has @rendermode InteractiveServer

Last Updated: 2025-01-27 (ReceiverUI ? WebUI migration complete)