feat(webui): migrate to hybrid Blazor architecture

Migrated `EnvelopeGenerator.ReceiverUI` to a new hybrid
Blazor architecture (`EnvelopeGenerator.WebUI`) combining
Blazor Server and WebAssembly modes. This resolves the
issue with `DxPdfViewer` requiring server-side rendering.

Key changes:
- Introduced `WebUI` (Blazor Server) and `WebUI.Client`
  (Blazor WebAssembly) projects.
- Added YARP reverse proxy to `WebUI` for API routing.
- Migrated client-side pages to `WebUI.Client` with
  `@rendermode InteractiveWebAssembly`.
- Migrated server-side pages (e.g., PDF viewer) to `WebUI`
  with `@rendermode InteractiveServer`.
- Copied services, models, and static files from `ReceiverUI`.
- Configured DevExpress server-side and WASM components.

Includes detailed migration documentation, rollback plan,
and testing strategies to ensure stability.
This commit is contained in:
2026-06-12 12:48:32 +02:00
parent 7fb1a87cf2
commit d35a35c75e
2 changed files with 782 additions and 0 deletions

781
MIGRATION_CONTEXT.md Normal file
View File

@@ -0,0 +1,781 @@
# EnvelopeGenerator.ReceiverUI ? WebUI Migration Context
## ?? Migration Purpose
### Problem Statement
**DevExpress `DxPdfViewer` component does NOT work in pure Blazor WebAssembly (standalone) mode.**
**Symptoms:**
- PDF file loads successfully (byte array received)
- Component renders (no errors in console)
- **Result: Blank/white screen** - PDF is not displayed
**Root Cause:**
DevExpress `DxPdfViewer` requires **backend server-side rendering services** that are NOT available in pure WebAssembly projects (`Microsoft.NET.Sdk.BlazorWebAssembly`).
**Solution:**
Migrate from **pure Blazor WebAssembly** (`ReceiverUI`) to **Blazor Auto (Server+WASM hybrid)** (`WebUI`) architecture.
---
## ?? Current vs. Target Architecture
### Current Architecture (PROBLEMATIC)
```
Client Browser
?
EnvelopeGenerator.API:8088 (YARP Proxy)
?
EnvelopeGenerator.ReceiverUI:52936 (Pure Blazor WebAssembly)
??? SDK: Microsoft.NET.Sdk.BlazorWebAssembly
??? DxPdfViewer ? ? WHITE SCREEN (no backend)
??? All pages run client-side only
```
### Target Architecture (SOLUTION)
```
Client Browser
?
EnvelopeGenerator.WebUI:XXXX (Blazor Auto - Server+WASM)
??? YARP Proxy: /api/* ? API:8088
??? YARP Proxy: /swagger/* ? API:8088
??? Server-side Pages (@rendermode InteractiveServer)
? ??? DxPdfViewer pages ? ? WORKS (backend available)
??? Client-side Pages (@rendermode InteractiveWebAssembly)
??? Login, Sender, Index pages
```
---
## ??? Project Structure
### EnvelopeGenerator.WebUI (Server Project)
**Path:** `EnvelopeGenerator.WebUI\EnvelopeGenerator.WebUI\`
**SDK:** `Microsoft.NET.Sdk.Web`
**Target:** `net8.0`
**Responsibilities:**
1. **YARP Reverse Proxy**
- Routes `/api/*` to `API:8088`
- Routes `/swagger/*`, `/openapi/*`, `/scalar/*` to `API:8088`
- All other routes handled by Blazor components
2. **DevExpress Server-Side Services**
- `AddDevExpressServerSideBlazorPdfViewer()` - **CRITICAL for DxPdfViewer**
- `AddDevExpressBlazorReportViewer()` - For DxReportViewer
- Provides backend rendering engine
3. **Static File Hosting**
- Serves `wwwroot/` (JS, CSS, PDF.js)
- Hosts PDF viewer assets
4. **Server-Side Components**
- `@rendermode InteractiveServer` pages
- PDF viewer pages (EnvelopeReceiverPage*.razor)
**Key Files:**
- `Program.cs` - YARP + DevExpress server configuration
- `yarp.json` - Proxy route definitions
- `Components/App.razor` - Root component
- `Components/Routes.razor` - Routing configuration
- `Pages/EnvelopeReceiverPage*.razor` - Server-side PDF viewers
**NuGet Packages:**
```xml
<PackageReference Include="Yarp.ReverseProxy" Version="2.1.0" />
<PackageReference Include="DevExpress.Blazor" Version="25.2.3" />
<PackageReference Include="DevExpress.Blazor.PdfViewer" Version="25.2.3" />
<PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.22" />
```
---
### EnvelopeGenerator.WebUI.Client (WASM Project)
**Path:** `EnvelopeGenerator.WebUI\EnvelopeGenerator.WebUI.Client\`
**SDK:** `Microsoft.NET.Sdk.BlazorWebAssembly`
**Target:** `net8.0`
**Responsibilities:**
1. **Client-Side Pages**
- `@rendermode InteractiveWebAssembly` components
- Authentication pages (Login)
- Public pages (Index)
- Sender dashboard
2. **Business Logic Services**
- All services (AuthService, DocumentService, etc.)
- API communication via HttpClient
- Signature caching
3. **DevExpress WASM Components**
- Client-side DevExpress components
- Reporting tools
**Key Files:**
- `Program.cs` - Service registration + DevExpress WASM
- `Pages/Index.razor` - Landing page
- `Pages/LoginSenderPage.razor` - Sender authentication
- `Pages/LoginReceiverPage.razor` - Receiver authentication
- `Pages/EnvelopeSenderPage.razor` - Sender dashboard
- `Services/*` - All business logic services
**NuGet Packages:**
```xml
<PackageReference Include="DevExpress.Blazor.PdfViewer" Version="25.2.3" />
<PackageReference Include="DevExpress.Blazor.Reporting.JSBasedControls" Version="25.2.3" />
<PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" />
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.2.3" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="8.3.1.2" />
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.119.1" />
<PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11" />
```
---
### EnvelopeGenerator.ReceiverUI (OLD - Will Remain for Now)
**Path:** `EnvelopeGenerator.ReceiverUI\`
**Status:** ?? **DEPRECATED but NOT deleted during migration**
**SDK:** `Microsoft.NET.Sdk.BlazorWebAssembly`
**Migration Plan:**
- Files will be **COPIED** to WebUI/WebUI.Client (not moved)
- Original ReceiverUI project will **remain untouched** during migration
- Can be deleted later after successful migration verification
---
## ?? File Migration Map
### Client-Side Pages (ReceiverUI ? WebUI.Client)
| Source File | Destination | Render Mode | Action |
|-------------|-------------|-------------|--------|
| `ReceiverUI/Pages/Index.razor` | `WebUI.Client/Pages/Index.razor` | `@rendermode InteractiveWebAssembly` | **COPY** |
| `ReceiverUI/Pages/EnvelopeSenderPage.razor` | `WebUI.Client/Pages/EnvelopeSenderPage.razor` | `@rendermode InteractiveWebAssembly` | **COPY** |
| `ReceiverUI/Pages/LoginSenderPage.razor` | `WebUI.Client/Pages/LoginSenderPage.razor` | `@rendermode InteractiveWebAssembly` | **COPY** |
| `ReceiverUI/Pages/LoginReceiverPage.razor` | `WebUI.Client/Pages/LoginReceiverPage.razor` | `@rendermode InteractiveWebAssembly` | **COPY** |
**Post-Migration Edit:**
Add `@rendermode InteractiveWebAssembly` to the top of each file (after `@page` directive).
---
### Server-Side PDF Viewer Pages (ReceiverUI ? WebUI)
| Source File | Destination | Render Mode | Action |
|-------------|-------------|-------------|--------|
| `ReceiverUI/Pages/EnvelopeReceiverPage.razor` | `WebUI/Pages/EnvelopeReceiverPage.razor` | `@rendermode InteractiveServer` | **COPY** |
| `ReceiverUI/Pages/EnvelopeReceiverPage_DxPdfViewer.razor` | `WebUI/Pages/EnvelopeReceiverPage_DxPdfViewer.razor` | `@rendermode InteractiveServer` | **COPY** |
| `ReceiverUI/Pages/EnvelopeReceiverPage_DxReportViewer.razor` | `WebUI/Pages/EnvelopeReceiverPage_DxReportViewer.razor` | `@rendermode InteractiveServer` | **COPY** |
| `ReceiverUI/Pages/EnvelopeReceiverPage_embed.razor` | `WebUI/Pages/EnvelopeReceiverPage_embed.razor` | `@rendermode InteractiveServer` | **COPY** |
**Why Server-Side?**
- DevExpress `DxPdfViewer` **requires** backend rendering
- `DxReportViewer` also needs server-side services
- `@rendermode InteractiveServer` provides SignalR connection to backend
**Post-Migration Edit:**
1. Add `@rendermode InteractiveServer` to top of file
2. Update `@using` directives to reference `WebUI.Client` namespaces
---
### Services (ReceiverUI ? WebUI.Client)
| Source Directory | Destination | Action |
|------------------|-------------|--------|
| `ReceiverUI/Services/AuthService.cs` | `WebUI.Client/Services/AuthService.cs` | **COPY** |
| `ReceiverUI/Services/DocumentService.cs` | `WebUI.Client/Services/DocumentService.cs` | **COPY** |
| `ReceiverUI/Services/SignatureService.cs` | `WebUI.Client/Services/SignatureService.cs` | **COPY** |
| `ReceiverUI/Services/SignatureCacheService.cs` | `WebUI.Client/Services/SignatureCacheService.cs` | **COPY** |
| `ReceiverUI/Services/EnvelopeReceiverService.cs` | `WebUI.Client/Services/EnvelopeReceiverService.cs` | **COPY** |
| `ReceiverUI/Services/AnnotationService.cs` | `WebUI.Client/Services/AnnotationService.cs` | **COPY** |
| `ReceiverUI/Services/AppVersionService.cs` | `WebUI.Client/Services/AppVersionService.cs` | **COPY** |
| `ReceiverUI/Services/CustomDataSourceWizardJsonDataConnectionStorage.cs` | `WebUI.Client/Services/CustomDataSourceWizardJsonDataConnectionStorage.cs` | **COPY** |
| `ReceiverUI/Services/CustomJsonDataConnectionProviderFactory.cs` | `WebUI.Client/Services/CustomJsonDataConnectionProviderFactory.cs` | **COPY** |
| `ReceiverUI/Services/CustomReportProvider.cs` | `WebUI.Client/Services/CustomReportProvider.cs` | **COPY** |
| `ReceiverUI/Services/FontLoader.cs` | `WebUI.Client/Services/FontLoader.cs` | **COPY** |
| `ReceiverUI/Services/InMemoryReportStorageWebExtension.cs` | `WebUI.Client/Services/InMemoryReportStorageWebExtension.cs` | **COPY** |
| `ReceiverUI/Services/ObjectDataSourceWizardCustomTypeProvider.cs` | `WebUI.Client/Services/ObjectDataSourceWizardCustomTypeProvider.cs` | **COPY** |
**Post-Migration Edit:**
Update namespace from `EnvelopeGenerator.ReceiverUI.Services` to `EnvelopeGenerator.WebUI.Client.Services`
---
### Models, Options, Data (ReceiverUI ? WebUI.Client)
| Source Directory | Destination | Action |
|------------------|-------------|--------|
| `ReceiverUI/Models/*` | `WebUI.Client/Models/*` | **COPY ALL FILES** |
| `ReceiverUI/Options/*` | `WebUI.Client/Options/*` | **COPY ALL FILES** |
| `ReceiverUI/Data/*` | `WebUI.Client/Data/*` | **COPY ALL FILES** |
| `ReceiverUI/Shared/*` | `WebUI.Client/Shared/*` | **COPY ALL FILES** |
**Post-Migration Edit:**
Update namespaces to `EnvelopeGenerator.WebUI.Client.*`
---
### Static Files (ReceiverUI ? WebUI)
| Source | Destination | Action |
|--------|-------------|--------|
| `ReceiverUI/wwwroot/js/*` | `WebUI/wwwroot/js/*` | **MERGE** (keep both if conflict) |
| `ReceiverUI/wwwroot/css/*` | `WebUI/wwwroot/css/*` | **MERGE** |
| `ReceiverUI/wwwroot/docs/*` | `WebUI/wwwroot/docs/*` | **MERGE** |
| `ReceiverUI/wwwroot/appsettings.json` | `WebUI/wwwroot/appsettings.json` | **MERGE** (combine PdfViewerOptions) |
| `ReceiverUI/wwwroot/appsettings.Development.json` | `WebUI/wwwroot/appsettings.Development.json` | **MERGE** |
**Critical Files:**
- `js/pdf-viewer.js` - PDF.js wrapper
- `js/receiver-signature.js` - Signature pad
- `css/envelope-viewer.css` - Viewer styles
- `appsettings.json` - PdfViewerOptions configuration
---
### _Imports.razor (ReceiverUI ? WebUI.Client)
| Source | Destination | Action |
|--------|-------------|--------|
| `ReceiverUI/_Imports.razor` | `WebUI.Client/_Imports.razor` | **MERGE** (combine using directives) |
**Post-Migration Content:**
```razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using EnvelopeGenerator.WebUI.Client
@using EnvelopeGenerator.WebUI.Client.Services
@using EnvelopeGenerator.WebUI.Client.Models
@using EnvelopeGenerator.WebUI.Client.Options
@using DevExpress.Blazor
@using DevExpress.Blazor.PdfViewer
@using DevExpress.Blazor.Reporting
```
---
### Excluded Files (NOT Migrated)
| Source Directory | Reason |
|------------------|--------|
| `ReceiverUI/Pages/Example/*` | Test pages, not needed in production |
| `ReceiverUI/PredefinedReports/*` | Deprecated, reports moved to WebUI.Client/Data |
---
## ?? YARP Configuration
### WebUI/yarp.json (NEW FILE)
```json
{
"ReverseProxy": {
"Routes": {
"api-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "/api/{**catch-all}"
}
},
"swagger-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "/swagger/{**catch-all}"
}
},
"openapi-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "/openapi/{**catch-all}"
}
},
"scalar-route": {
"ClusterId": "api-cluster",
"Match": {
"Path": "/scalar/{**catch-all}"
}
}
},
"Clusters": {
"api-cluster": {
"Destinations": {
"api-destination": {
"Address": "https://localhost:8088"
}
}
}
}
}
}
```
**Purpose:**
- Route all `/api/*` requests to backend API
- Route Swagger/OpenAPI to API (development only)
- All other requests handled by Blazor components
**Configuration Location:**
Place `yarp.json` in `EnvelopeGenerator.WebUI\EnvelopeGenerator.WebUI\` directory.
**Important:**
Set **Copy to Output Directory** to `Copy if newer` in file properties.
---
### API/yarp.json (MODIFICATION)
**Current State:**
API currently proxies requests to ReceiverUI:52936
**Target State:**
API will **NOT have YARP** - all proxying moves to WebUI
**Action:**
- **DO NOT DELETE** `API/yarp.json` during migration
- Keep for rollback safety
- Can comment out YARP code in `API/Program.cs` instead of deleting
---
## ?? Configuration Files
### WebUI/appsettings.json
```json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ApiOptions": {
"BaseUrl": ""
},
"PdfViewerOptions": {
"ThumbnailBaseScale": 0.75,
"ThumbnailEnableHiDPI": true,
"MainCanvasEnableHiDPI": true,
"ZoomStepPercentage": 5
}
}
```
**Critical Setting:**
`"BaseUrl": ""` - Empty because YARP handles proxy automatically (requests to `/api/*` are proxied)
---
### WebUI/wwwroot/appsettings.json (Client-Side Config)
```json
{
"ApiOptions": {
"BaseUrl": ""
},
"PdfViewerOptions": {
"ThumbnailBaseScale": 0.75,
"ThumbnailEnableHiDPI": true,
"ThumbnailMaxDPR": 4.0,
"MainCanvasEnableHiDPI": true,
"MainCanvasMaxDPR": 4.0,
"EnableSmoothZoom": true,
"ZoomTransitionDuration": 200,
"RenderingOpacity": 1.0,
"ZoomStepPercentage": 5,
"ThumbnailRenderDelay": 25
}
}
```
**Purpose:**
Configures PDF.js quality settings for optimal rendering.
---
## ?? Render Mode Strategy
### When to Use `@rendermode InteractiveServer`
**Use Cases:**
- PDF viewer pages (DxPdfViewer, DxReportViewer)
- Any page using DevExpress components requiring backend
- Pages with heavy server-side logic
**Example:**
```razor
@page "/envelope/{EnvelopeKey}"
@rendermode InteractiveServer
@using DevExpress.Blazor.PdfViewer
@using EnvelopeGenerator.WebUI.Client.Services
...
```
**Why:**
DevExpress `DxPdfViewer` calls server-side APIs for PDF rendering. Server render mode provides SignalR connection to backend.
---
### When to Use `@rendermode InteractiveWebAssembly`
**Use Cases:**
- Authentication pages (no sensitive data on client)
- Public pages (landing, index)
- Pages with pure client-side logic
- Sender dashboard (calls API via HttpClient)
**Example:**
```razor
@page "/sender/login"
@rendermode InteractiveWebAssembly
@using EnvelopeGenerator.WebUI.Client.Services
...
```
**Why:**
No server-side dependencies, reduces server load, works offline (PWA).
---
## ?? Service Registration
### WebUI/Program.cs (Server)
```csharp
using DevExpress.Blazor;
var builder = WebApplication.CreateBuilder(args);
// Load YARP configuration
builder.Configuration.AddJsonFile("yarp.json", optional: false, reloadOnChange: true);
// Blazor Components
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
// YARP Reverse Proxy
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
// DevExpress Server-Side Services (CRITICAL for DxPdfViewer)
builder.Services.AddDevExpressBlazor(configure => configure.BootstrapVersion = BootstrapVersion.v5);
builder.Services.AddDevExpressServerSideBlazorPdfViewer();
builder.Services.AddDevExpressBlazorReportViewer();
// Configuration Options
builder.Services.Configure<EnvelopeGenerator.WebUI.Client.Options.ApiOptions>(
builder.Configuration.GetSection("ApiOptions"));
builder.Services.Configure<EnvelopeGenerator.WebUI.Client.Options.PdfViewerOptions>(
builder.Configuration.GetSection("PdfViewerOptions"));
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
// Blazor routing (BEFORE YARP)
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(EnvelopeGenerator.WebUI.Client._Imports).Assembly);
// YARP proxy (AFTER Blazor - catch-all)
app.MapReverseProxy();
app.Run();
```
**Critical Order:**
1. `MapRazorComponents` first (Blazor routes)
2. `MapReverseProxy` last (catch-all for `/api/*`)
---
### WebUI.Client/Program.cs (WASM)
```csharp
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using EnvelopeGenerator.WebUI.Client.Services;
using EnvelopeGenerator.WebUI.Client.Options;
using DevExpress.Blazor.Reporting;
using DevExpress.XtraReports.Web.Extensions;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// HTTP Client (uses WebUI's YARP proxy)
builder.Services.AddScoped(sp => new HttpClient {
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
// Configuration Options
builder.Services.Configure<ApiOptions>(
builder.Configuration.GetSection(ApiOptions.SectionName));
builder.Services.Configure<PdfViewerOptions>(
builder.Configuration.GetSection(PdfViewerOptions.SectionName));
// Business Services
builder.Services.AddScoped<DocumentService>();
builder.Services.AddScoped<AuthService>();
builder.Services.AddScoped<AnnotationService>();
builder.Services.AddScoped<EnvelopeReceiverService>();
builder.Services.AddScoped<SignatureService>();
builder.Services.AddScoped<SignatureCacheService>();
builder.Services.AddSingleton<AppVersionService>();
// DevExpress WASM
builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer();
builder.Services.AddDevExpressWebAssemblyBlazorReportViewer();
builder.Services.AddDevExpressBlazorReportingWebAssembly(configure => {
configure.UseDevelopmentMode();
});
// Reporting Services
builder.Services.AddScoped<IDataSourceWizardJsonConnectionStorage, CustomDataSourceWizardJsonDataConnectionStorage>();
builder.Services.AddScoped<IJsonDataConnectionProviderFactory, CustomJsonDataConnectionProviderFactory>();
builder.Services.AddScoped<IObjectDataSourceWizardTypeProvider, ObjectDataSourceWizardCustomTypeProvider>();
DevExpress.Utils.DeserializationSettings.RegisterTrustedClass(typeof(EnvelopeGenerator.WebUI.Client.Data.DataItemList));
DevExpress.Utils.DeserializationSettings.RegisterTrustedClass(typeof(EnvelopeGenerator.WebUI.Client.PredefinedReports.Report));
builder.Services.AddSingleton<InMemoryReportStorageWebExtension>();
builder.Services.AddSingleton<ReportStorageWebExtension>(sp => sp.GetRequiredService<InMemoryReportStorageWebExtension>());
builder.Services.AddScoped<IReportProviderAsync, CustomReportProvider>();
ReportStorageWebExtension.RegisterExtensionGlobal(new InMemoryReportStorageWebExtension());
await builder.Build().RunAsync();
```
---
## ? Migration Checklist
### Phase 1: WebUI YARP Setup
- [ ] Create `WebUI/yarp.json`
- [ ] Add YARP NuGet package to `WebUI.csproj`
- [ ] Update `WebUI/Program.cs` with YARP configuration
- [ ] Add DevExpress server packages to `WebUI.csproj`
- [ ] Create `WebUI/appsettings.json` with `ApiOptions.BaseUrl = ""`
### Phase 2: File Migration - Client Pages
- [ ] Copy `ReceiverUI/Pages/Index.razor` ? `WebUI.Client/Pages/`
- [ ] Copy `ReceiverUI/Pages/EnvelopeSenderPage.razor` ? `WebUI.Client/Pages/`
- [ ] Copy `ReceiverUI/Pages/LoginSenderPage.razor` ? `WebUI.Client/Pages/`
- [ ] Copy `ReceiverUI/Pages/LoginReceiverPage.razor` ? `WebUI.Client/Pages/`
- [ ] Add `@rendermode InteractiveWebAssembly` to each file
### Phase 3: File Migration - Server Pages
- [ ] Copy `ReceiverUI/Pages/EnvelopeReceiverPage.razor` ? `WebUI/Pages/`
- [ ] Copy `ReceiverUI/Pages/EnvelopeReceiverPage_DxPdfViewer.razor` ? `WebUI/Pages/`
- [ ] Copy `ReceiverUI/Pages/EnvelopeReceiverPage_DxReportViewer.razor` ? `WebUI/Pages/`
- [ ] Copy `ReceiverUI/Pages/EnvelopeReceiverPage_embed.razor` ? `WebUI/Pages/`
- [ ] Add `@rendermode InteractiveServer` to each file
- [ ] Update `@using` directives to `WebUI.Client` namespaces
### Phase 4: File Migration - Services
- [ ] Copy `ReceiverUI/Services/*` ? `WebUI.Client/Services/`
- [ ] Update namespaces to `EnvelopeGenerator.WebUI.Client.Services`
### Phase 5: File Migration - Models/Options/Data
- [ ] Copy `ReceiverUI/Models/*` ? `WebUI.Client/Models/`
- [ ] Copy `ReceiverUI/Options/*` ? `WebUI.Client/Options/`
- [ ] Copy `ReceiverUI/Data/*` ? `WebUI.Client/Data/`
- [ ] Copy `ReceiverUI/Shared/*` ? `WebUI.Client/Shared/`
- [ ] Update all namespaces to `EnvelopeGenerator.WebUI.Client.*`
### Phase 6: File Migration - Static Files
- [ ] Merge `ReceiverUI/wwwroot/js/*` ? `WebUI/wwwroot/js/`
- [ ] Merge `ReceiverUI/wwwroot/css/*` ? `WebUI/wwwroot/css/`
- [ ] Merge `ReceiverUI/wwwroot/docs/*` ? `WebUI/wwwroot/docs/`
- [ ] Merge `ReceiverUI/wwwroot/appsettings.json` ? `WebUI/wwwroot/appsettings.json`
### Phase 7: Configuration
- [ ] Add DevExpress WASM packages to `WebUI.Client.csproj`
- [ ] Update `WebUI.Client/Program.cs` with service registrations
- [ ] Merge `ReceiverUI/_Imports.razor` ? `WebUI.Client/_Imports.razor`
- [ ] Set `yarp.json` to "Copy if newer" in file properties
### Phase 8: Testing
- [ ] Build `WebUI` project ? No errors
- [ ] Build `WebUI.Client` project ? No errors
- [ ] Run `WebUI` ? Application starts
- [ ] Test YARP: `/api/*` routes to API:8088
- [ ] Test `/sender/login` ? Login page works
- [ ] Test `/envelope/DxPdfViewer` ? **PDF displays (not blank!)**
- [ ] Test `/envelope/{key}` ? PDF.js viewer works
- [ ] Test signature caching
- [ ] Test authentication cookies
- [ ] No CORS errors in browser console
---
## ?? Critical Notes
### 1. DO NOT DELETE ReceiverUI During Migration
- Keep `EnvelopeGenerator.ReceiverUI` project untouched
- Files are **COPIED**, not moved
- Allows rollback if migration fails
- Can be deleted after successful verification
### 2. API YARP Modification Strategy
- **DO NOT delete** `API/yarp.json` immediately
- Comment out YARP code in `API/Program.cs` instead
- Keep for rollback safety
- Remove only after WebUI proven stable
### 3. Namespace Updates Are CRITICAL
- All copied files must update namespaces
- `EnvelopeGenerator.ReceiverUI.*` ? `EnvelopeGenerator.WebUI.Client.*`
- Missing namespace updates = compilation errors
### 4. Render Mode MUST Match Component Type
- Server-side DevExpress ? `@rendermode InteractiveServer`
- Client-side pages ? `@rendermode InteractiveWebAssembly`
- Wrong render mode = component won't work
### 5. Service Registration Must Be Complete
- All ReceiverUI services ? `WebUI.Client/Program.cs`
- Missing service = runtime injection errors
- Check DI container for all dependencies
### 6. YARP Route Order Matters
```csharp
app.MapRazorComponents<App>()...; // FIRST - Blazor routes
app.MapReverseProxy(); // LAST - Catch-all API proxy
```
Wrong order = `/api/*` requests handled by Blazor (404 errors)
### 7. wwwroot/ Merge Strategy
- **DO NOT overwrite** existing WebUI files
- Compare manually before merge
- Prioritize ReceiverUI files (they're tested)
- Keep both if conflict, rename WebUI version
---
## ?? Rollback Plan
If migration fails:
### Immediate Rollback
1. Stop `WebUI` application
2. Restore `API/Program.cs` YARP code
3. Start `ReceiverUI` + `API` as before
4. System operational again
### Permanent Rollback
1. Checkout previous Git commit
2. Discard all `WebUI` changes
3. Delete copied files from `WebUI.Client`
4. Resume development on `ReceiverUI`
### Partial Rollback
1. Keep `WebUI` structure
2. Revert YARP changes only
3. Continue using `ReceiverUI` while debugging `WebUI`
---
## ?? Success Criteria
Migration is successful when:
- [ ] `WebUI` application starts without errors
- [ ] `/api/*` requests proxied to API:8088
- [ ] `/envelope/DxPdfViewer` displays PDF (not blank!)
- [ ] `/envelope/{key}` PDF.js viewer works
- [ ] Login pages functional (Sender, Receiver)
- [ ] Sender dashboard loads
- [ ] Signature caching works
- [ ] Authentication cookies work
- [ ] No CORS errors
- [ ] No console errors
- [ ] Performance acceptable (not slower than ReceiverUI)
---
## ?? Known Issues & Workarounds
### Issue 1: DxPdfViewer Still Blank
**Cause:** Missing DevExpress server-side services
**Fix:** Verify `AddDevExpressServerSideBlazorPdfViewer()` in `WebUI/Program.cs`
### Issue 2: 404 on /api/* Requests
**Cause:** YARP not configured or wrong route order
**Fix:** Check `yarp.json` exists and `MapReverseProxy()` is AFTER `MapRazorComponents()`
### Issue 3: Services Not Injected
**Cause:** Missing service registration in `WebUI.Client/Program.cs`
**Fix:** Copy ALL service registrations from `ReceiverUI/Program.cs`
### Issue 4: Namespace Errors
**Cause:** Namespace not updated after file copy
**Fix:** Find/Replace `EnvelopeGenerator.ReceiverUI` ? `EnvelopeGenerator.WebUI.Client`
### Issue 5: Static Files Not Loading
**Cause:** `wwwroot/` not merged correctly
**Fix:** Check file paths, ensure files copied to `WebUI/wwwroot/`
---
## ?? Git Workflow
### Branch Strategy
- **Current Branch:** `bugfix/devexpress-pdf-not-displaying`
- **Remote:** `https://git.dd/AppStd/EnvelopeGenerator`
### Commit Strategy
1. Commit after each phase (incremental changes)
2. Descriptive commit messages
3. Tag final working version: `v1.0.0-webui-migration`
### Example Commits
```
feat(webui): add YARP configuration for API proxy
feat(webui): migrate client-side pages from ReceiverUI
feat(webui): migrate server-side PDF viewer pages
feat(webui): migrate services and models
feat(webui): merge static files and configuration
fix(webui): update namespaces to WebUI.Client
test(webui): verify DxPdfViewer rendering
docs(migration): add MIGRATION_CONTEXT.md
```
---
## ?? Learning Resources
### DevExpress Blazor Render Modes
https://docs.devexpress.com/Blazor/403507/blazor-components-render-modes
### YARP Documentation
https://microsoft.github.io/reverse-proxy/
### Blazor Auto (InteractiveAuto) Mode
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes
---
**Migration Status:** ? **READY TO START**
**Last Updated:** 2025-01-XX
**Responsible:** AI Agent + Developer
**Estimated Time:** 4-6 hours
**Risk Level:** Medium (rollback available)
---
## ?? Support
**Questions?** Contact the development team or refer to:
- `COPILOT_CONTEXT.md` - Overall project context
- `FORM_APPLICATION_CONTEXT.md` - Legacy VB.NET app context
- DevExpress Support Portal
---
**END OF MIGRATION CONTEXT**