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.
782 lines
26 KiB
Markdown
782 lines
26 KiB
Markdown
# 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**
|