diff --git a/MailPrint/Controllers/PrintController.cs b/MailPrint/Controllers/PrintController.cs
index 96cf208..a97a14b 100644
--- a/MailPrint/Controllers/PrintController.cs
+++ b/MailPrint/Controllers/PrintController.cs
@@ -23,7 +23,7 @@ public class PrintController : ControllerBase
///
/// PDF hochladen und drucken.
/// POST /api/print/upload
- /// Form-Data: file, printer (opt), paperSource (opt), copies (opt)
+ /// Form-Data: file, printer (opt, Druckername oder Profilname), paperSource (opt), copies (opt)
///
[HttpPost("upload")]
[Consumes("multipart/form-data")]
@@ -34,11 +34,11 @@ public class PrintController : ControllerBase
[FromForm] int copies = 0)
{
if (file is null || file.Length == 0)
- return BadRequest(new { error = "Keine Datei." });
+ return BadRequest(new { success = false, error = "Keine Datei." });
var ext = Path.GetExtension(file.FileName).ToLower();
if (!_options.AllowedExtensions.Contains(ext))
- return BadRequest(new { error = $"Dateityp '{ext}' nicht erlaubt." });
+ return BadRequest(new { success = false, error = $"Dateityp '{ext}' nicht erlaubt." });
Directory.CreateDirectory(_options.TempDirectory);
var tempFile = Path.Combine(_options.TempDirectory, $"{Guid.NewGuid()}{ext}");
@@ -48,7 +48,7 @@ public class PrintController : ControllerBase
await using (var fs = System.IO.File.Create(tempFile))
await file.CopyToAsync(fs);
- _printService.PrintPdf(new PrintJob
+ var r = _printService.PrintPdf(new PrintJob
{
FilePath = tempFile,
MailSubject = file.FileName,
@@ -58,12 +58,16 @@ public class PrintController : ControllerBase
CopiesOverride = copies > 0 ? copies : null
});
- return Ok(new { success = true, file = file.FileName, printer, paperSource });
+ return Ok(new { success = true, file = file.FileName, profile = r.Profile, printer = r.Printer, paperSource = r.PaperSource, copies = r.Copies });
+ }
+ catch (PrintConfigException ex)
+ {
+ return BadRequest(new { success = false, error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "WebAPI Druckfehler: {File}", file.FileName);
- return StatusCode(500, new { error = ex.Message });
+ return StatusCode(500, new { success = false, error = ex.Message });
}
}
@@ -75,7 +79,7 @@ public class PrintController : ControllerBase
public async Task PrintUrl([FromBody] PrintUrlRequest request)
{
if (!Uri.TryCreate(request.Url, UriKind.Absolute, out var uri))
- return BadRequest(new { error = "Ungültige URL." });
+ return BadRequest(new { success = false, error = "Ungültige URL." });
Directory.CreateDirectory(_options.TempDirectory);
var tempFile = Path.Combine(_options.TempDirectory, $"{Guid.NewGuid()}.pdf");
@@ -85,7 +89,7 @@ public class PrintController : ControllerBase
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
await System.IO.File.WriteAllBytesAsync(tempFile, await http.GetByteArrayAsync(uri));
- _printService.PrintPdf(new PrintJob
+ var r = _printService.PrintPdf(new PrintJob
{
FilePath = tempFile,
MailSubject = uri.Segments.LastOrDefault() ?? "download.pdf",
@@ -95,12 +99,16 @@ public class PrintController : ControllerBase
CopiesOverride = request.Copies > 0 ? request.Copies : null
});
- return Ok(new { success = true, url = request.Url, printer = request.Printer, paperSource = request.PaperSource });
+ return Ok(new { success = true, url = request.Url, profile = r.Profile, printer = r.Printer, paperSource = r.PaperSource, copies = r.Copies });
+ }
+ catch (PrintConfigException ex)
+ {
+ return BadRequest(new { success = false, error = ex.Message });
}
catch (Exception ex)
{
_logger.LogError(ex, "WebAPI URL-Druckfehler: {Url}", request.Url);
- return StatusCode(500, new { error = ex.Message });
+ return StatusCode(500, new { success = false, error = ex.Message });
}
}
diff --git a/MailPrint/Services/PrintService.cs b/MailPrint/Services/PrintService.cs
index e8b3492..8d6a9f4 100644
--- a/MailPrint/Services/PrintService.cs
+++ b/MailPrint/Services/PrintService.cs
@@ -23,25 +23,31 @@ public class PrintService
_options = options.Value;
}
- public void PrintPdf(PrintJob job)
+ ///
+ /// Druckt das PDF. Wirft bei unbekanntem Drucker/Profil,
+ /// sonst Exception bei jedem Druckfehler (inkl. SumatraPDF-ExitCode ≠ 0).
+ ///
+ public PrintResult PrintPdf(PrintJob job)
{
- // Profil auflösen: direkte Overrides > benanntes Profil > erstes Profil > Defaults
- var profile = ResolveProfile(job);
- var printerName = !string.IsNullOrEmpty(job.PrinterOverride) ? job.PrinterOverride : profile.PrinterName;
- var paperSource = !string.IsNullOrEmpty(job.PaperSourceOverride) ? job.PaperSourceOverride : profile.PaperSource;
- var copies = job.CopiesOverride ?? profile.Copies;
-
- if (string.IsNullOrEmpty(printerName))
- printerName = GetDefaultPrinter();
-
- _logger.LogInformation("Drucke '{Subject}' → {Printer} | Fach: {Fach} | {Copies}x",
- job.MailSubject, printerName, string.IsNullOrEmpty(paperSource) ? "Standard" : paperSource, copies);
-
try
{
- for (int i = 0; i < copies; i++)
- PrintOnce(job.FilePath, printerName, paperSource, profile.Duplex);
+ var r = Resolve(job);
+
+ _logger.LogInformation(
+ "Drucke '{Subject}' → {Printer} | Profil: {Profile} | Fach: {Fach} | Duplex: {Duplex} | {Copies}x",
+ job.MailSubject, r.Printer, string.IsNullOrEmpty(r.Profile) ? "-" : r.Profile,
+ string.IsNullOrEmpty(r.PaperSource) ? "Standard" : r.PaperSource, r.Duplex, r.Copies);
+
+ for (int i = 0; i < r.Copies; i++)
+ PrintOnce(job.FilePath, r.Printer, r.PaperSource, r.Duplex);
+
_logger.LogInformation("Druck OK: {File}", job.FilePath);
+ return r;
+ }
+ catch (PrintConfigException ex)
+ {
+ _logger.LogWarning("Druck abgelehnt: {Message} ({File})", ex.Message, job.FilePath);
+ throw;
}
catch (Exception ex)
{
@@ -54,15 +60,58 @@ public class PrintService
}
}
- private PrinterProfile ResolveProfile(PrintJob job)
+ ///
+ /// Auflösung: Override (Profilname oder Druckername) > Profil aus Postfach/Ordner > erstes Profil > Defaults.
+ /// Ein Override der einem Profilnamen entspricht wird zum Profil aufgelöst (Drucker, Fach, Duplex, Kopien).
+ ///
+ private PrintResult Resolve(PrintJob job)
{
- if (!string.IsNullOrEmpty(job.PrinterProfileName))
+ var printerOverride = job.PrinterOverride?.Trim();
+ PrinterProfile? profile = null;
+
+ if (!string.IsNullOrEmpty(printerOverride))
{
- var p = _options.PrinterProfiles.FirstOrDefault(x =>
- x.Name.Equals(job.PrinterProfileName, StringComparison.OrdinalIgnoreCase));
- if (p != null) return p;
+ profile = FindProfile(printerOverride);
+ if (profile != null) printerOverride = null;
}
- return _options.PrinterProfiles.FirstOrDefault() ?? new PrinterProfile();
+
+ if (profile == null && !string.IsNullOrWhiteSpace(job.PrinterProfileName))
+ profile = FindProfile(job.PrinterProfileName)
+ ?? throw new PrintConfigException(
+ $"Unbekanntes Drucker-Profil '{job.PrinterProfileName}'. Vorhanden: {ProfileNames()}");
+
+ profile ??= _options.PrinterProfiles.FirstOrDefault() ?? new PrinterProfile();
+
+ var requested = !string.IsNullOrEmpty(printerOverride) ? printerOverride : profile.PrinterName;
+ var printer = string.IsNullOrWhiteSpace(requested) ? GetDefaultPrinter() : MatchInstalledPrinter(requested);
+
+ var paperSource = !string.IsNullOrEmpty(job.PaperSourceOverride) ? job.PaperSourceOverride : profile.PaperSource;
+ var copies = Math.Max(1, job.CopiesOverride ?? profile.Copies);
+
+ return new PrintResult(profile.Name, printer, paperSource ?? "", profile.Duplex ?? "none", copies);
+ }
+
+ private PrinterProfile? FindProfile(string name) =>
+ _options.PrinterProfiles.FirstOrDefault(x =>
+ x.Name.Equals(name.Trim(), StringComparison.OrdinalIgnoreCase));
+
+ private string ProfileNames() =>
+ _options.PrinterProfiles.Count == 0 ? "-" : string.Join(", ", _options.PrinterProfiles.Select(p => p.Name));
+
+ private string MatchInstalledPrinter(string name)
+ {
+ var installed = PrinterSettings.InstalledPrinters.Cast().ToList();
+ return installed.FirstOrDefault(p => p.Equals(name, StringComparison.OrdinalIgnoreCase))
+ ?? throw new PrintConfigException(
+ $"Drucker oder Profil '{name}' nicht gefunden. Profile: {ProfileNames()} | Drucker: {string.Join(", ", installed)}");
+ }
+
+ private static string GetDefaultPrinter()
+ {
+ var name = new PrinterSettings().PrinterName;
+ if (string.IsNullOrWhiteSpace(name))
+ throw new PrintConfigException("Kein Drucker angegeben und kein Standarddrucker vorhanden.");
+ return name;
}
private void PrintOnce(string pdfPath, string printerName, string paperSource, string duplex = "none")
@@ -78,8 +127,9 @@ public class PrintService
// Fallback Shell-Print
_logger.LogWarning("Kein SumatraPDF – Shell-Print (kein Papierfach-Support)");
var psi = new ProcessStartInfo { FileName = pdfPath, Verb = "print", UseShellExecute = true, WindowStyle = ProcessWindowStyle.Hidden };
- using var p = Process.Start(psi)!;
- p.WaitForExit(30_000);
+ using var p = Process.Start(psi) ?? throw new InvalidOperationException("Shell-Print nicht startbar/bestätigbar.");
+ if (!p.WaitForExit(30_000)) throw new TimeoutException("Timeout: Shell-Print");
+ if (p.ExitCode != 0) throw new InvalidOperationException($"Shell-Print meldet Fehler (ExitCode {p.ExitCode}).");
}
private static string BuildPrintSettings(string paperSource, string duplex)
@@ -103,7 +153,8 @@ public class PrintService
var psi = new ProcessStartInfo(exe, args) { UseShellExecute = false, CreateNoWindow = true };
using var p = Process.Start(psi) ?? throw new InvalidOperationException($"Nicht startbar: {exe}");
if (!p.WaitForExit(300_000)) { p.Kill(); throw new TimeoutException($"Timeout: {exe}"); }
- if (p.ExitCode != 0) _logger.LogWarning("ExitCode {Code}: {Exe}", p.ExitCode, exe);
+ if (p.ExitCode != 0)
+ throw new InvalidOperationException($"SumatraPDF meldet Fehler (ExitCode {p.ExitCode}).");
}
public List GetPrinterInfos()
@@ -121,7 +172,7 @@ public class PrintService
}
public IEnumerable GetAvailablePrinters() => GetPrinterInfos().Select(p => p.Name);
- private static string GetDefaultPrinter() => new PrinterSettings().PrinterName;
+
private void TryDelete(string path)
{
try { if (File.Exists(path)) File.Delete(path); }
@@ -130,3 +181,9 @@ public class PrintService
}
public record PrinterInfo(string Name, List PaperSources);
+
+/// Tatsächlich verwendete Druckparameter nach Auflösung von Profil/Override.
+public record PrintResult(string Profile, string Printer, string PaperSource, string Duplex, int Copies);
+
+/// Ungültige Anfrage/Konfiguration (unbekannter Drucker oder Profil) – kein Systemfehler.
+public class PrintConfigException(string message) : Exception(message);