Compare commits
4 Commits
36025fad7b
...
c2d8b6531c
| Author | SHA1 | Date | |
|---|---|---|---|
| c2d8b6531c | |||
| 665266457f | |||
| 14d581acac | |||
| eb533a1dc2 |
41
EnvelopeGenerator.Service/EnvelopeGenerator.Service.csproj
Normal file
41
EnvelopeGenerator.Service/EnvelopeGenerator.Service.csproj
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net4.6.2</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>dotnet-EnvelopeGenerator.Service-816256a4-c636-4313-92c0-b62b04040c2b</UserSecretsId>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.0" />
|
||||||
|
<PackageReference Include="NLog" Version="5.3.1" />
|
||||||
|
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.9" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- DigitalData & Quartz: local packages, must match CommonServices versions exactly -->
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="DigitalData.Modules.Database">
|
||||||
|
<HintPath>..\packages\DigitalData.Modules.Database.2.3.5.4\lib\net462\DigitalData.Modules.Database.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DigitalData.Modules.Encryption">
|
||||||
|
<HintPath>..\packages\DigitalData.Modules.Encryption.1.3.1\lib\net462\DigitalData.Modules.Encryption.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DigitalData.Modules.Logging">
|
||||||
|
<HintPath>..\packages\DigitalData.Modules.Logging.2.6.5\lib\net462\DigitalData.Modules.Logging.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
<!-- Quartz 3.8.0 <20> must match CommonServices (same AppDomain, one version) -->
|
||||||
|
<Reference Include="Quartz">
|
||||||
|
<HintPath>..\packages\Quartz.3.8.0\lib\net462\Quartz.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\EnvelopeGenerator.CommonServices\EnvelopeGenerator.CommonServices.vbproj" />
|
||||||
|
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
</Project>
|
||||||
28
EnvelopeGenerator.Service/LogConfigFactory.cs
Normal file
28
EnvelopeGenerator.Service/LogConfigFactory.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using DigitalData.Modules.Logging;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Builds a <see cref="LogConfig"/> from <see cref="ServiceConfig"/>.
|
||||||
|
/// Required because CommonServices jobs have a hard dependency on <see cref="LogConfig"/>
|
||||||
|
/// in their Quartz <c>JobDataMap</c>.
|
||||||
|
/// Log path mirrors the legacy behavior: <c><BaseDirectory>\Log</c>.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
315
EnvelopeGenerator.Service/MIGRATION_PLAN.md
Normal file
315
EnvelopeGenerator.Service/MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# 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** via `New TempFiles(LogConfig)`.
|
||||||
|
This is called on every job execution and manages the job's own temp lifecycle. It expects `LogConfig`.
|
||||||
|
**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 using `ILogger<TempFiles>`.
|
||||||
|
|
||||||
|
### Finding 3 — LogConfig is a Hard Dependency of All Jobs
|
||||||
|
`FinalizeDocumentJob.Execute()` and `APIEnvelopeJob.Execute()` both do:
|
||||||
|
```vb
|
||||||
|
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 legacy `My.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` (for `AddWindowsService()`)
|
||||||
|
- Add **project reference** to `EnvelopeGenerator.CommonServices` (vbproj)
|
||||||
|
- Add **project reference** to `EnvelopeGenerator.Domain` (csproj)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Configuration (`ServiceConfig.cs`)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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`:
|
||||||
|
```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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> `FontStyle` integer: Regular=0, Bold=1, **Italic=2**, Underline=4, Strikeout=8.
|
||||||
|
> All values mirror `PDFBurnerParams.vb` defaults 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`:
|
||||||
|
```csharp
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddNLog("nlog.config");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. LogConfigFactory (`LogConfigFactory.cs`)
|
||||||
|
|
||||||
|
New static helper — bridges `ServiceConfig` ? `LogConfig` (required by CommonServices jobs):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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>`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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 files
|
||||||
|
- `CleanUp()` ? delete the directory on service stop
|
||||||
|
|
||||||
|
> **Does NOT replace** `CommonServices.TempFiles` — that one continues to be used
|
||||||
|
> inside each job via `LogConfig`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Schedulers
|
||||||
|
|
||||||
|
### `Schedulers/SchedulerFinishEnvelope.cs`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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`
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
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):
|
||||||
|
- ~~`ProjectInstaller`~~ ? replaced by `sc create` / `New-Service`
|
||||||
|
- ~~`Service.Designer.vb`~~ ? BackgroundService handles this
|
||||||
|
- ~~`Service.resx`~~ ? not applicable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
41
EnvelopeGenerator.Service/Program.cs
Normal file
41
EnvelopeGenerator.Service/Program.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using EnvelopeGenerator.Service;
|
||||||
|
using EnvelopeGenerator.Service.Schedulers;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using NLog.Extensions.Logging;
|
||||||
|
|
||||||
|
var builder = Host.CreateApplicationBuilder(args);
|
||||||
|
|
||||||
|
// NLog
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddNLog("nlog.config");
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
builder.Services.Configure<ServiceConfig>(
|
||||||
|
builder.Configuration.GetSection("ServiceConfig"));
|
||||||
|
|
||||||
|
// Resolve connection string once for singleton registrations
|
||||||
|
var connectionString = builder.Configuration.GetConnectionString("Default")
|
||||||
|
?? throw new InvalidOperationException("ConnectionStrings:Default is not configured.");
|
||||||
|
|
||||||
|
// Services
|
||||||
|
builder.Services.AddSingleton<TempFiles>();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<SchedulerFinishEnvelope>(sp => new SchedulerFinishEnvelope(
|
||||||
|
sp.GetRequiredService<ILogger<SchedulerFinishEnvelope>>(),
|
||||||
|
sp.GetRequiredService<IOptions<ServiceConfig>>(),
|
||||||
|
connectionString));
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<SchedulerEnvelopeTaskApi>(sp => new SchedulerEnvelopeTaskApi(
|
||||||
|
sp.GetRequiredService<ILogger<SchedulerEnvelopeTaskApi>>(),
|
||||||
|
sp.GetRequiredService<IOptions<ServiceConfig>>(),
|
||||||
|
connectionString));
|
||||||
|
|
||||||
|
builder.Services.AddHostedService<Worker>();
|
||||||
|
|
||||||
|
// Windows Service (SCM) support — replaces legacy ProjectInstaller/installutil
|
||||||
|
builder.Services.AddWindowsService(options =>
|
||||||
|
options.ServiceName = "EnvelopeGenerator.Service");
|
||||||
|
|
||||||
|
var host = builder.Build();
|
||||||
|
host.Run();
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>bin\Release\net4.6.2\publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<_TargetId>Folder</_TargetId>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
12
EnvelopeGenerator.Service/Properties/launchSettings.json
Normal file
12
EnvelopeGenerator.Service/Properties/launchSettings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"EnvelopeGenerator.Service": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"DOTNET_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
EnvelopeGenerator.Service/QuartzLogProvider.cs
Normal file
60
EnvelopeGenerator.Service/QuartzLogProvider.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using Quartz.Logging;
|
||||||
|
using QuartzLogLevel = Quartz.Logging.LogLevel;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bridges Quartz's <see cref="ILogProvider"/> to <see cref="ILogger"/>.
|
||||||
|
/// Mirrors the legacy <c>LogProvider.vb</c> behavior.
|
||||||
|
/// <c>OpenNestedContext</c> and <c>OpenMappedContext</c> are not implemented (same as legacy).
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class QuartzLogProvider : ILogProvider
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
public QuartzLogProvider(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logger GetLogger(string name)
|
||||||
|
{
|
||||||
|
return (level, func, exception, parameters) =>
|
||||||
|
{
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
_logger.LogError(exception, exception.Message);
|
||||||
|
}
|
||||||
|
else if (func != null)
|
||||||
|
{
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case QuartzLogLevel.Trace:
|
||||||
|
case QuartzLogLevel.Debug:
|
||||||
|
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||||
|
_logger.LogDebug(func(), parameters);
|
||||||
|
break;
|
||||||
|
case QuartzLogLevel.Info:
|
||||||
|
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information))
|
||||||
|
_logger.LogInformation(func(), parameters);
|
||||||
|
break;
|
||||||
|
case QuartzLogLevel.Warn:
|
||||||
|
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning))
|
||||||
|
_logger.LogWarning(func(), parameters);
|
||||||
|
break;
|
||||||
|
case QuartzLogLevel.Error:
|
||||||
|
case QuartzLogLevel.Fatal:
|
||||||
|
if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error))
|
||||||
|
_logger.LogError(func(), parameters);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable OpenNestedContext(string message) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public IDisposable OpenMappedContext(string key, object value, bool destructure = false) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using DigitalData.Modules.Logging;
|
||||||
|
using EnvelopeGenerator.CommonServices.Jobs;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Quartz;
|
||||||
|
using Quartz.Logging;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Service.Schedulers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules <see cref="APIEnvelopeJob"/> via Quartz.
|
||||||
|
/// C# rewrite of the legacy <c>Scheduler_Envelopetask_API.vb</c>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SchedulerEnvelopeTaskApi
|
||||||
|
{
|
||||||
|
private readonly ILogger<SchedulerEnvelopeTaskApi> _logger;
|
||||||
|
private readonly ServiceConfig _config;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
private IScheduler? _scheduler;
|
||||||
|
|
||||||
|
private const string JobName = "SendInvitationMailsAPI";
|
||||||
|
|
||||||
|
public SchedulerEnvelopeTaskApi(
|
||||||
|
ILogger<SchedulerEnvelopeTaskApi> logger,
|
||||||
|
IOptions<ServiceConfig> config,
|
||||||
|
string connectionString)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config.Value;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start(LogConfig logConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Starting SchedulerEnvelopeTaskApi...");
|
||||||
|
|
||||||
|
LogProvider.SetCurrentLogProvider(new QuartzLogProvider(_logger));
|
||||||
|
|
||||||
|
_scheduler = await SchedulerBuilder
|
||||||
|
.Create(new NameValueCollection())
|
||||||
|
.UseDefaultThreadPool(x => x.MaxConcurrency = 5)
|
||||||
|
.BuildScheduler();
|
||||||
|
|
||||||
|
var jobKey = new JobKey(JobName);
|
||||||
|
|
||||||
|
var jobData = new JobDataMap
|
||||||
|
{
|
||||||
|
{ Value.LOGCONFIG, logConfig },
|
||||||
|
{ Value.DATABASE, _connectionString }
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogDebug("Initialized Job [{JobName}]", JobName);
|
||||||
|
|
||||||
|
var job = JobBuilder.Create<APIEnvelopeJob>()
|
||||||
|
.UsingJobData(jobData)
|
||||||
|
.WithIdentity(jobKey)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var trigger = TriggerBuilder.Create()
|
||||||
|
.ForJob(jobKey)
|
||||||
|
.WithIdentity($"{JobName}-trigger")
|
||||||
|
.WithSimpleSchedule(s => s
|
||||||
|
.RepeatForever()
|
||||||
|
.WithIntervalInMinutes(_config.IntervalInMin))
|
||||||
|
.StartNow()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_logger.LogDebug("{JobName}-trigger initialized", JobName);
|
||||||
|
|
||||||
|
await _scheduler.ScheduleJob(job, trigger);
|
||||||
|
_logger.LogDebug("{JobName} scheduled", JobName);
|
||||||
|
|
||||||
|
await _scheduler.Start();
|
||||||
|
_logger.LogInformation("{JobName} started", JobName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "SchedulerEnvelopeTaskApi failed to start");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop()
|
||||||
|
{
|
||||||
|
if (_scheduler is null) return;
|
||||||
|
_logger.LogInformation("Stopping SchedulerEnvelopeTaskApi...");
|
||||||
|
await _scheduler.Shutdown();
|
||||||
|
_logger.LogInformation("SchedulerEnvelopeTaskApi stopped!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
using System.Collections.Specialized;
|
||||||
|
using DigitalData.Modules.Logging;
|
||||||
|
using EnvelopeGenerator.CommonServices.Jobs;
|
||||||
|
using EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Quartz;
|
||||||
|
using Quartz.Logging;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Service.Schedulers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Schedules <see cref="FinalizeDocumentJob"/> via Quartz.
|
||||||
|
/// C# rewrite of the legacy <c>Scheduler_FinishEnvelope.vb</c>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SchedulerFinishEnvelope
|
||||||
|
{
|
||||||
|
private readonly ILogger<SchedulerFinishEnvelope> _logger;
|
||||||
|
private readonly ServiceConfig _config;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
private IScheduler? _scheduler;
|
||||||
|
|
||||||
|
private const string JobName = "CertificateDocumentJob";
|
||||||
|
|
||||||
|
public SchedulerFinishEnvelope(
|
||||||
|
ILogger<SchedulerFinishEnvelope> logger,
|
||||||
|
IOptions<ServiceConfig> config,
|
||||||
|
string connectionString)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config.Value;
|
||||||
|
_connectionString = connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start(string licenseKey, LogConfig logConfig)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Starting SchedulerFinishEnvelope...");
|
||||||
|
|
||||||
|
LogProvider.SetCurrentLogProvider(new QuartzLogProvider(_logger));
|
||||||
|
|
||||||
|
_scheduler = await SchedulerBuilder
|
||||||
|
.Create(new NameValueCollection())
|
||||||
|
.UseDefaultThreadPool(x => x.MaxConcurrency = 5)
|
||||||
|
.BuildScheduler();
|
||||||
|
|
||||||
|
var jobKey = new JobKey(JobName);
|
||||||
|
|
||||||
|
var jobData = new JobDataMap
|
||||||
|
{
|
||||||
|
{ Value.GDPICTURE, licenseKey },
|
||||||
|
{ Value.LOGCONFIG, logConfig },
|
||||||
|
{ Value.DATABASE, _connectionString },
|
||||||
|
{ Value.PDF_BURNER_PARAMS, _config.PDFBurnerParams }
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.LogDebug("Initialized Job [{JobName}]", JobName);
|
||||||
|
|
||||||
|
var job = JobBuilder.Create<FinalizeDocumentJob>()
|
||||||
|
.UsingJobData(jobData)
|
||||||
|
.WithIdentity(jobKey)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var trigger = TriggerBuilder.Create()
|
||||||
|
.ForJob(jobKey)
|
||||||
|
.WithIdentity($"{JobName}-trigger")
|
||||||
|
.WithSimpleSchedule(s => s
|
||||||
|
.RepeatForever()
|
||||||
|
.WithIntervalInMinutes(_config.IntervalInMin))
|
||||||
|
.StartNow()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_logger.LogDebug("Initialized Trigger");
|
||||||
|
|
||||||
|
await _scheduler.ScheduleJob(job, trigger);
|
||||||
|
_logger.LogDebug("Job scheduled.");
|
||||||
|
|
||||||
|
await _scheduler.Start();
|
||||||
|
_logger.LogDebug("Scheduler started!");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "SchedulerFinishEnvelope failed to start");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Stop()
|
||||||
|
{
|
||||||
|
if (_scheduler is null) return;
|
||||||
|
_logger.LogInformation("Stopping SchedulerFinishEnvelope...");
|
||||||
|
await _scheduler.Shutdown();
|
||||||
|
_logger.LogInformation("SchedulerFinishEnvelope stopped!");
|
||||||
|
}
|
||||||
|
}
|
||||||
14
EnvelopeGenerator.Service/ServiceConfig.cs
Normal file
14
EnvelopeGenerator.Service/ServiceConfig.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service-level configuration bound from the "ServiceConfig" section of appsettings.json.
|
||||||
|
/// Connection string is read separately via the standard "ConnectionStrings:Default" key.
|
||||||
|
/// </summary>
|
||||||
|
public class ServiceConfig
|
||||||
|
{
|
||||||
|
public bool Debug { get; set; } = false;
|
||||||
|
public int IntervalInMin { get; set; } = 1;
|
||||||
|
public PDFBurnerParams PDFBurnerParams { get; set; } = new PDFBurnerParams();
|
||||||
|
}
|
||||||
75
EnvelopeGenerator.Service/TempFiles.cs
Normal file
75
EnvelopeGenerator.Service/TempFiles.cs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
namespace EnvelopeGenerator.Service;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service-level temp file management. Mirrors <c>TempFiles.vb</c> from the legacy project.
|
||||||
|
/// Manages <c>%TEMP%\EnvelopeGenerator</c> at host startup and shutdown.
|
||||||
|
///
|
||||||
|
/// NOTE: This is independent from <c>EnvelopeGenerator.CommonServices.TempFiles</c>, which is
|
||||||
|
/// instantiated inside each Quartz job via <see cref="DigitalData.Modules.Logging.LogConfig"/>.
|
||||||
|
/// Do not confuse the two.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class TempFiles
|
||||||
|
{
|
||||||
|
private readonly ILogger<TempFiles> _logger;
|
||||||
|
|
||||||
|
public string TempPath { get; }
|
||||||
|
|
||||||
|
public TempFiles(ILogger<TempFiles> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
TempPath = Path.Combine(Path.GetTempPath(), "EnvelopeGenerator");
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Create()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(TempPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(TempPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CleanUpFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to create temp directory: {TempPath}", TempPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanUpFiles()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.GetFiles(TempPath))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Deleting temp file: {File}", file);
|
||||||
|
File.Delete(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to clean up temp files in: {TempPath}", TempPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CleanUp()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Deleting temp directory: {TempPath}", TempPath);
|
||||||
|
Directory.Delete(TempPath, recursive: true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to delete temp directory: {TempPath}", TempPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
EnvelopeGenerator.Service/Worker.cs
Normal file
111
EnvelopeGenerator.Service/Worker.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using DigitalData.Modules.Database;
|
||||||
|
using EnvelopeGenerator.Service.Schedulers;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Service;
|
||||||
|
|
||||||
|
public sealed class Worker : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<Worker> _logger;
|
||||||
|
private readonly ServiceConfig _config;
|
||||||
|
private readonly string _connectionString;
|
||||||
|
private readonly TempFiles _tempFiles;
|
||||||
|
private readonly SchedulerFinishEnvelope _schedulerFinishEnvelope;
|
||||||
|
private readonly SchedulerEnvelopeTaskApi _schedulerEnvelopeTaskApi;
|
||||||
|
|
||||||
|
public Worker(
|
||||||
|
ILogger<Worker> logger,
|
||||||
|
IOptions<ServiceConfig> config,
|
||||||
|
IConfiguration configuration,
|
||||||
|
TempFiles tempFiles,
|
||||||
|
SchedulerFinishEnvelope schedulerFinishEnvelope,
|
||||||
|
SchedulerEnvelopeTaskApi schedulerEnvelopeTaskApi)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config.Value;
|
||||||
|
_connectionString = configuration.GetConnectionString("Default")
|
||||||
|
?? throw new InvalidOperationException("ConnectionStrings:Default is not configured.");
|
||||||
|
_tempFiles = tempFiles;
|
||||||
|
_schedulerFinishEnvelope = schedulerFinishEnvelope;
|
||||||
|
_schedulerEnvelopeTaskApi = schedulerEnvelopeTaskApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("EnvelopeGenerator.Service starting...");
|
||||||
|
|
||||||
|
// 1. Validate connection string
|
||||||
|
if (string.IsNullOrWhiteSpace(_connectionString))
|
||||||
|
throw new InvalidOperationException("Connection string is empty!");
|
||||||
|
|
||||||
|
// 2. Build LogConfig (required by CommonServices jobs via JobDataMap)
|
||||||
|
var logConfig = LogConfigFactory.Create(_config);
|
||||||
|
_logger.LogInformation("DEBUG = {Debug}", _config.Debug);
|
||||||
|
|
||||||
|
// 3. Connect to DB and load GDPicture license
|
||||||
|
_logger.LogDebug("Initializing Database...");
|
||||||
|
var database = new MSSQLServer(logConfig, _connectionString);
|
||||||
|
|
||||||
|
if (!database.DBInitialized)
|
||||||
|
throw new InvalidOperationException("Database connection could not be established!");
|
||||||
|
|
||||||
|
_logger.LogDebug("Database initialized.");
|
||||||
|
|
||||||
|
var licenseKey = database.GetScalarValue(
|
||||||
|
"SELECT LICENSE FROM TBDD_3RD_PARTY_MODULES WHERE NAME = 'GDPICTURE' AND ACTIVE = 1") as string;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(licenseKey))
|
||||||
|
throw new InvalidOperationException("GDPicture license could not be loaded!");
|
||||||
|
|
||||||
|
_logger.LogDebug("GDPicture license loaded.");
|
||||||
|
|
||||||
|
// 4. Create service-level temp directory
|
||||||
|
_tempFiles.Create();
|
||||||
|
|
||||||
|
// 5. Start FinalizeDocument scheduler
|
||||||
|
_logger.LogDebug("Initializing schedulers...");
|
||||||
|
await _schedulerFinishEnvelope.Start(licenseKey!, logConfig);
|
||||||
|
|
||||||
|
// 6. Delay between schedulers (legacy behavior preserved)
|
||||||
|
await Task.Delay(2500, stoppingToken);
|
||||||
|
|
||||||
|
// 7. Start API envelope scheduler
|
||||||
|
await _schedulerEnvelopeTaskApi.Start(logConfig);
|
||||||
|
|
||||||
|
_logger.LogInformation("EnvelopeGenerator.Service started!");
|
||||||
|
|
||||||
|
// 8. Wait for cancellation (service runs until stopped)
|
||||||
|
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Normal shutdown <20> not an error
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "EnvelopeGenerator.Service encountered a fatal error during startup.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("EnvelopeGenerator.Service stopping...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _schedulerFinishEnvelope.Stop();
|
||||||
|
await _schedulerEnvelopeTaskApi.Stop();
|
||||||
|
_tempFiles.CleanUp();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error during service shutdown.");
|
||||||
|
}
|
||||||
|
|
||||||
|
await base.StopAsync(cancellationToken);
|
||||||
|
_logger.LogInformation("EnvelopeGenerator.Service stopped!");
|
||||||
|
}
|
||||||
|
}
|
||||||
8
EnvelopeGenerator.Service/appsettings.Development.json
Normal file
8
EnvelopeGenerator.Service/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
EnvelopeGenerator.Service/appsettings.json
Normal file
24
EnvelopeGenerator.Service/appsettings.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
|
||||||
|
},
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
20
EnvelopeGenerator.Service/nlog.config
Normal file
20
EnvelopeGenerator.Service/nlog.config
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
autoReload="true">
|
||||||
|
|
||||||
|
<targets async="true">
|
||||||
|
<target xsi:type="File"
|
||||||
|
name="file"
|
||||||
|
fileName="${basedir}/Log/service-${shortdate}.log"
|
||||||
|
layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message}${onexception:${newline}${exception:format=tostring}}" />
|
||||||
|
<target xsi:type="Console"
|
||||||
|
name="console"
|
||||||
|
layout="${longdate} | ${level:uppercase=true} | ${logger} | ${message}" />
|
||||||
|
</targets>
|
||||||
|
|
||||||
|
<rules>
|
||||||
|
<logger name="*" minlevel="Debug" writeTo="file,console" />
|
||||||
|
</rules>
|
||||||
|
|
||||||
|
</nlog>
|
||||||
@@ -9,8 +9,6 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EnvelopeGenerator.CommonSer
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Web", "EnvelopeGenerator.Web\EnvelopeGenerator.Web.csproj", "{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Web", "EnvelopeGenerator.Web\EnvelopeGenerator.Web.csproj", "{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EnvelopeGenerator.Service", "EnvelopeGenerator.Service\EnvelopeGenerator.Service.vbproj", "{83ED2617-B398-4859-8F59-B38F8807E83E}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Domain", "EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj", "{4F32A98D-E6F0-4A09-BD97-1CF26107E837}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Domain", "EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj", "{4F32A98D-E6F0-4A09-BD97-1CF26107E837}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Infrastructure", "EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj", "{63E32615-0ECA-42DC-96E3-91037324B7C7}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Infrastructure", "EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj", "{63E32615-0ECA-42DC-96E3-91037324B7C7}"
|
||||||
@@ -55,6 +53,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Server",
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Server.Client", "EnvelopeGenerator.Server\EnvelopeGenerator.Server.Client\EnvelopeGenerator.Server.Client.csproj", "{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Server.Client", "EnvelopeGenerator.Server\EnvelopeGenerator.Server.Client\EnvelopeGenerator.Server.Client.csproj", "{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EnvelopeGenerator.Service_legacy", "EnvelopeGenerator.Service_legacy\EnvelopeGenerator.Service_legacy.vbproj", "{83ED2617-B398-4859-8F59-B38F8807E83E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Service", "EnvelopeGenerator.Service\EnvelopeGenerator.Service.csproj", "{6B87FB5F-F4EA-45BE-A218-385DC2CE8E80}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -73,10 +75,6 @@ Global
|
|||||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Release|Any CPU.Build.0 = Debug|Any CPU
|
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Release|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
@@ -121,6 +119,14 @@ Global
|
|||||||
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{83ED2617-B398-4859-8F59-B38F8807E83E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{83ED2617-B398-4859-8F59-B38F8807E83E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6B87FB5F-F4EA-45BE-A218-385DC2CE8E80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6B87FB5F-F4EA-45BE-A218-385DC2CE8E80}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6B87FB5F-F4EA-45BE-A218-385DC2CE8E80}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6B87FB5F-F4EA-45BE-A218-385DC2CE8E80}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -129,7 +135,6 @@ Global
|
|||||||
{089D5634-FB6B-42D0-B912-7AA7457044E7} = {0CBC2432-A561-4440-89BC-671B66A24146}
|
{089D5634-FB6B-42D0-B912-7AA7457044E7} = {0CBC2432-A561-4440-89BC-671B66A24146}
|
||||||
{6EA0C51F-C2B1-4462-8198-3DE0B32B74F8} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
{6EA0C51F-C2B1-4462-8198-3DE0B32B74F8} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||||
{83ED2617-B398-4859-8F59-B38F8807E83E} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
|
||||||
{4F32A98D-E6F0-4A09-BD97-1CF26107E837} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
{4F32A98D-E6F0-4A09-BD97-1CF26107E837} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||||
{63E32615-0ECA-42DC-96E3-91037324B7C7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{63E32615-0ECA-42DC-96E3-91037324B7C7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
{5A9984F8-51A2-4558-A415-EC5FEED7CF7D} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
{5A9984F8-51A2-4558-A415-EC5FEED7CF7D} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||||
@@ -145,6 +150,8 @@ Global
|
|||||||
{BF1700D5-592E-4FFA-84E8-5480E289A1F0} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
{BF1700D5-592E-4FFA-84E8-5480E289A1F0} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||||
{4E6C54DA-576D-0955-2564-9EC890BB8279} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0}
|
{4E6C54DA-576D-0955-2564-9EC890BB8279} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0}
|
||||||
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0}
|
{9C41A0B1-6AFE-AFC5-CE33-08052F45DAEF} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0}
|
||||||
|
{83ED2617-B398-4859-8F59-B38F8807E83E} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||||
|
{6B87FB5F-F4EA-45BE-A218-385DC2CE8E80} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}
|
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}
|
||||||
|
|||||||
Reference in New Issue
Block a user