Updated documentation to reflect the migration from WebUI to EnvelopeGenerator.Server, including a new "Documentation Catalog" in `AGENTS.md`. Adjusted route mappings, render modes, and directory structure for Blazor components. Revised coordinate system documentation and CSS for status colors to align with the new architecture. Updated YARP proxy and PDF.js configuration paths. Migrated `MIGRATION_PLAN.md` to modernize `EnvelopeGenerator.Service` with C# Worker Service equivalents. Updated `FORM_APPLICATION_CONTEXT.md` and `RECEIVER_PDF_VIEWER_CONTEXT.md` to reflect the new architecture and planned viewer integration. Fixed German translation issues in `fix-report-label-read-and-confirmed.md`. Standardized formatting and terminology across all files.
12 KiB
Migration Plan: EnvelopeGenerator.Service_legacy ? EnvelopeGenerator.Service
Revision v3 — Full deep analysis complete
Overview
Migrate the legacy VB.NET Windows Service (EnvelopeGenerator.Service_legacy) to a modern
C# Worker Service (EnvelopeGenerator.Service) targeting .NET Framework 4.6.2, using
Microsoft.Extensions.Hosting, IConfiguration, ILogger<T> and NLog.
Deep Analysis Findings (vs. v1/v2)
Finding 1 — Connection String: Plain Text, No Encryption Required
FinalizeDocumentJob and APIEnvelopeJob call MSSQLServer.DecryptConnectionString() on the
JobDataMap[Value.DATABASE] value. However, MSSQLServer.DecryptConnectionString() is a pass-through
for plain-text strings — no wrapping/encryption step is needed.
Resolution: The connection string is read from IConfiguration via the standard
ConnectionStrings:Default key (same key as appsettings.Database.json in EnvelopeGenerator.Web).
It is passed as-is to both the Worker-level MSSQLServer and the JobDataMap. No
EncryptConnectionString() call is made anywhere in the new service.
Finding 2 — Dual TempFiles Architecture
There are two independent TempFiles classes:
EnvelopeGenerator.CommonServices.TempFiles— used inside each job viaNew TempFiles(LogConfig). This is called on every job execution and manages the job's own temp lifecycle. It expectsLogConfig. We do not touch this.EnvelopeGenerator.Service_legacy.TempFiles— used at the service/host level (startup cleanup, shutdown cleanup). This is the one we rewrite in C# in the Service project usingILogger<TempFiles>.
Finding 3 — LogConfig is a Hard Dependency of All Jobs
FinalizeDocumentJob.Execute() and APIEnvelopeJob.Execute() both do:
LogConfig = pContext.MergedJobDataMap.Item(Value.LOGCONFIG)
Logger = LogConfig.GetLogger()
myTempFiles = New TempFiles(LogConfig) ' ? CommonServices.TempFiles
LogConfig is not optional. The schedulers must construct a valid LogConfig and place it in the
JobDataMap. It must carry the correct log path, debug flag, and application identity.
Resolution: A LogConfigFactory static helper class will be created in the Service project to
build a LogConfig from ServiceConfig values:
LogPath=Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log")(mirrors legacyMy.Application.Info.DirectoryPath + "\Log")Debug=ServiceConfig.Debug- Application name =
"EnvelopeGenerator.Service"
Finding 4 — ProjectInstaller is Obsolete
ProjectInstaller.vb was the old installutil.exe mechanism for Windows Service registration.
With AddWindowsService() + sc create / PowerShell New-Service, this is completely replaced.
No equivalent is needed in the new project.
Finding 5 — GDPicture License SQL
The legacy service queries: SELECT LICENSE FROM TBDD_3RD_PARTY_MODULES WHERE NAME = 'GDPICTURE' and ACTIVE = 1
This must be replicated exactly in the Worker startup sequence. If the result is null/empty, startup fails
with a descriptive exception (matching legacy behavior).
Finding 6 — Quartz Version Mismatch
CommonServices uses Quartz 3.8.0. Service_legacy uses Quartz 3.15.0.
The new EnvelopeGenerator.Service must use Quartz 3.8.0 to match CommonServices (same AppDomain,
same version must be loaded). The legacy version bump in Service_legacy was inconsistent.
1. Project File (EnvelopeGenerator.Service.csproj)
- Add NuGet packages:
NLog(5.x, matching CommonServices'NLog.5.0.5)NLog.Extensions.Logging(latest net462-compatible)Quartz(3.8.0 — must match CommonServices)Microsoft.Extensions.Hosting.WindowsServices(forAddWindowsService())
- Add project reference to
EnvelopeGenerator.CommonServices(vbproj) - Add project reference to
EnvelopeGenerator.Domain(csproj)
2. Configuration (ServiceConfig.cs)
public class ServiceConfig
{
public string ConnectionString { get; set; } = string.Empty;
public bool Debug { get; set; } = false;
public int IntervalInMin { get; set; } = 1;
public PDFBurnerParams PDFBurnerParams { get; set; } = new PDFBurnerParams();
}
Connection string is read via the standard ConnectionStrings:Default key — consistent with
appsettings.Database.json used by EnvelopeGenerator.Web:
appsettings.json:
{
"ConnectionStrings": {
"Default": ""
},
"ServiceConfig": {
"Debug": false,
"IntervalInMin": 1,
"PDFBurnerParams": {
"IgnoredLabels": [ "Date", "Datum", "ZIP", "PLZ", "Place", "Ort", "Position", "Stellung" ],
"TopMargin": 0.1,
"YOffset": -0.3,
"FontName": "Arial",
"FontSize": 8,
"FontStyle": 2,
"TruncationSuffix": "..",
"TextMaxWidths": {
"position": 1.41,
"city": 1.41,
"date": 2.0
}
}
}
}
FontStyleinteger: Regular=0, Bold=1, Italic=2, Underline=4, Strikeout=8. All values mirrorPDFBurnerParams.vbdefaults exactly.
3. NLog Setup
nlog.config: File target ? ${basedir}/Log/service-${shortdate}.log, async wrapper,
layout with timestamp + level + logger + message + exception.
Program.cs: Wire via NLog.Extensions.Logging:
builder.Logging.ClearProviders();
builder.Logging.AddNLog("nlog.config");
4. LogConfigFactory (LogConfigFactory.cs)
New static helper — bridges ServiceConfig ? LogConfig (required by CommonServices jobs):
internal static class LogConfigFactory
{
public static LogConfig Create(ServiceConfig config)
{
var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Log");
var logConfig = new LogConfig(
LogConfig.PathType.CustomPath, logPath, null,
"Digital Data", "EnvelopeGenerator.Service");
logConfig.Debug = config.Debug;
return logConfig;
}
}
This LogConfig instance is what gets placed into every JobDataMap[Value.LOGCONFIG].
5. Quartz LogProvider (QuartzLogProvider.cs)
C# rewrite of LogProvider.vb — bridges Quartz ILogProvider ? ILogger<T>:
internal class QuartzLogProvider : ILogProvider
{
private readonly ILogger _logger;
public QuartzLogProvider(ILogger logger) => _logger = logger;
public Logger GetLogger(string name) => (level, func, ex, parameters) => { ... };
// OpenNestedContext / OpenMappedContext ? NotImplementedException (same as legacy)
}
6. TempFiles (TempFiles.cs) — Service-level only
Service-level rewrite of Service_legacy/TempFiles.vb using ILogger<TempFiles>:
Create()? create%TEMP%\EnvelopeGenerator, or clean existing filesCleanUp()? delete the directory on service stop
Does NOT replace
CommonServices.TempFiles— that one continues to be used inside each job viaLogConfig.
7. Schedulers
Schedulers/SchedulerFinishEnvelope.cs
public class SchedulerFinishEnvelope
{
// Constructor: ILogger<SchedulerFinishEnvelope>, IOptions<ServiceConfig>
// Start(licenseKey, logConfig):
// - Build JobDataMap:
// [GDPICTURE] = licenseKey
// [LOGCONFIG] = logConfig ? LogConfigFactory output
// [DATABASE] = MSSQLServer.EncryptConnectionString(connectionString)
// [PDF_BURNER_PARAMS] = config.PDFBurnerParams
// - Schedule FinalizeDocumentJob with Quartz (interval from config)
// - Register QuartzLogProvider
// Stop(): Scheduler.Shutdown()
}
Schedulers/SchedulerEnvelopeTaskApi.cs
public class SchedulerEnvelopeTaskApi
{
// Constructor: ILogger<SchedulerEnvelopeTaskApi>, IOptions<ServiceConfig>
// Start(logConfig):
// - Build JobDataMap:
// [LOGCONFIG] = logConfig
// [DATABASE] = MSSQLServer.EncryptConnectionString(connectionString)
// - Schedule APIEnvelopeJob with Quartz (interval from config)
// - Register QuartzLogProvider
// Stop(): Scheduler.Shutdown()
}
8. Worker (Worker.cs)
Full lifecycle replacing the placeholder:
ExecuteAsync:
1. Read ServiceConfig from IOptions<ServiceConfig>
2. Validate ConnectionString ? throw if empty
3. Build LogConfig via LogConfigFactory
4. Connect to DB (plain connection string — Worker-level only)
5. Query: SELECT LICENSE FROM TBDD_3RD_PARTY_MODULES WHERE NAME='GDPICTURE' AND ACTIVE=1
? throw if null/empty (matches legacy behavior)
6. TempFiles.Create()
7. await SchedulerFinishEnvelope.Start(licenseKey, logConfig)
8. await Task.Delay(2500, stoppingToken) ? preserved from legacy
9. await SchedulerEnvelopeTaskApi.Start(logConfig)
10. await Task.Delay(Timeout.Infinite, stoppingToken) ? wait for cancellation
StopAsync override:
11. await SchedulerFinishEnvelope.Stop()
12. await SchedulerEnvelopeTaskApi.Stop()
13. TempFiles.CleanUp()
9. Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddNLog("nlog.config");
builder.Services.Configure<ServiceConfig>(
builder.Configuration.GetSection("ServiceConfig"));
builder.Services.AddSingleton<TempFiles>();
builder.Services.AddSingleton<SchedulerFinishEnvelope>();
builder.Services.AddSingleton<SchedulerEnvelopeTaskApi>();
builder.Services.AddHostedService<Worker>();
builder.Services.AddWindowsService(o => o.ServiceName = "EnvelopeGenerator.Service");
var host = builder.Build();
host.Run();
10. Files to Create / Modify
| File | Action | Notes |
|---|---|---|
EnvelopeGenerator.Service.csproj |
Modify | Add packages, project refs |
Program.cs |
Modify | Full DI/NLog/WindowsService setup |
Worker.cs |
Modify | Full service lifecycle |
ServiceConfig.cs |
Create | |
LogConfigFactory.cs |
Create | Critical bridge: ServiceConfig ? LogConfig |
TempFiles.cs |
Create | Service-level only; CommonServices.TempFiles untouched |
QuartzLogProvider.cs |
Create | |
Schedulers/SchedulerFinishEnvelope.cs |
Create | |
Schedulers/SchedulerEnvelopeTaskApi.cs |
Create | |
appsettings.json |
Create | All PDFBurnerParams explicit |
nlog.config |
Create |
Not created (obsolete in new architecture):
? replaced byProjectInstallersc create/New-Service? BackgroundService handles thisService.Designer.vb? not applicableService.resx
11. Key Design Decisions
| Decision | Rationale |
|---|---|
| Quartz 3.8.0 (not 3.15.0) | Must match CommonServices; same AppDomain, one version |
| Connection string plain-text, no encryption | MSSQLServer.DecryptConnectionString() is a pass-through for plain strings; ConnectionStrings:Default key used (same convention as Web project) |
Connection string in ConnectionStrings:Default |
Consistent with rest of the solution (appsettings.Database.json); ServiceConfig holds only service-specific settings |
| LogConfigFactory | Jobs have hard dependency on LogConfig in JobDataMap; this is the clean bridge without leaking LogConfig into the DI container |
| 2500ms delay preserved | Legacy behavior; ensures Scheduler1 is fully initialized before Scheduler2 starts |
AddWindowsService() |
Native SCM integration; replaces ProjectInstaller/installutil entirely |
| NLog log path = BaseDirectory/Log | Mirrors My.Application.Info.DirectoryPath + "\Log" from legacy |
| CommonServices.TempFiles untouched | Jobs create their own TempFiles internally via LogConfig; service-level TempFiles is separate concern |
Approval Required
Please review this plan and confirm before implementation begins.