Zitat von tron1
Ich nehme das hier https://github.com/stephanstapel/ZUGFeRD-csharp. Man muss sich zwar mal kurz rein arbeiten, aber funktioniert ganz gut.
Nutze ich auch. Es ging hier um die Einbettung der XML-Rechnung in das PDF. PDF in PDF/A-3B konvertiere ich mit PdfSharpCore – etwas tricky, dafür kostenlos. Die Einbettung dann mit SyncFusion. Funktioniert mitlerweile tadellos.
Danke, auf SyncFusion bin ich schon gekommen und habe bereits eine Anfrage am Laufen.
Mit Ghostscript ginge es auch, wie ich gesehen habe, aber das Ding ist mir etwas zu wackelig. Ich denke, es wird SyncFusion werden, denn damit kann ich alles zentral machen.
Hallo zusammen,
ich stehe vor folgendem Szenario und suche Erfahrungsaustausch, Tipps oder Best Practices:
Ich habe bereits PDF/A-3 Dateien, die mit Ghostscript erfolgreich und normgerecht erzeugt werden.
Ich möchte eine ZUGFeRD-Rechnung (XML-Datei) normenkonform in diese PDF/A-3 einbetten, inklusive korrekter Verschlagwortung (AFRelationship, XMP-Metadaten etc.).
Die PDF/A-3 muss nach wie vor vollständig standardkonform bleiben (PDF/A-3b oder 3u).
Das Ganze soll automatisiert ablaufen, idealerweise aus einer .NET-Anwendung heraus.
Die XML-Datei wird aus einer firmeneigenen Datenstruktur (z. B. Rechnung-4711.XMLDATA) erzeugt. Diese Datenstruktur erzeugt unser ERP-System und enthält alles, was für die Erstellung einer ZUGFeRD-Rechnung oder XRechnung notwendig ist. Natürlich können auch andere Programme/ERP-Systeme diese Datenstruktur liefern und somit unser Tool nutzen.
Ich suche eine Lösung, die möglichst einmalig bezahlt werden kann (keine laufenden Lizenzkosten).
Die Einbettung kann (muss aber nicht) über ein externes Tool (CLI/EXE) aufgerufen werden können, d. h. keine unbedingte Integration als Bibliothek zwingend erforderlich.
Die Lizenz soll erlauben, das Tool im eigenen Produkt an Kunden weiterzugeben (OEM-fähig).
Open Source ist willkommen, solange es praktikabel ist.
Lösungen mit Java (ZUGFeRD-injector) oder Go (pdfcpu) sind möglich, wenn sie stabil und standardkonform sind.
PdfSharpCore kann PDF manipulieren, aber bietet keine PDF/A-3-Konformität, keine XMP- oder AFRelationship-Unterstützung. Selbstprogrammierung ist extrem aufwendig und fehleranfällig.
pdfcpu kann Anhänge einbetten, aber nicht normkonform verschlagworten (kein AFRelationship, kein XMP für ZUGFeRD).
ZUGFeRD-injector (Java CLI) ist praktisch und normkonform, aber bringt eine Java-Abhängigkeit mit.
callas pdfaPilot CLI/SDK ist professionell, normkonform, erlaubt OEM-Vertrieb, kostet aber ca. 5.000 € einmalig.
iText 7 Community (AGPL) kann alles, aber erfordert Offenlegung des Quellcodes für das Tool, wenn es weitergegeben wird. Kommerzielle Lizenz teuer.
Wie habt ihr die normkonforme Einbettung von ZUGFeRD/XML in PDF/A-3 technisch gelöst?
Welche Tools (Open Source oder kommerziell) setzt ihr ein – und wie sind eure Erfahrungen damit?
Nutzt ihr eher eine Bibliothek (z. B. iText, PdfBox, pdfcpu) oder ein externes CLI-Tool?
Wie handhabt ihr die Lizenzfragen, speziell bei Einbindung in kommerzielle Produkte?
Gibt es Empfehlungen für einfach bedienbare, zuverlässige Lösungen mit minimalen Lizenz- und Betriebsabhängigkeiten?
Vielen Dank schon mal für eure Empfehlungen!
LG
René
Ich verstehe dein Argument, aber der switch ist hier nicht unnötig. Er sorgt dafür, dass ClosedXML-spezifische Typen direkt in gängige .NET-Typen umgewandelt werden, was den Code robuster macht. Klar, wenn eine stark typisierte Lösung gewünscht ist, wäre eine eigene Klasse besser, aber das hängt vom Anwendungsfall ab. Mein Ziel war es, eine moderne und flexible Variante zu zeigen, die universell funktioniert.
LG
René
Abgesehen von der Antwort meines Vorredners solltest du darüber nachdenken, LINQ zu verwenden. Das macht den Code lesbarer, sicherer und moderner.
private XLTBL ImportXL(IXLWorksheet WS)
{
return WS.RangeUsed()?.Rows()
.Select(row => row.Cells()
.Select<IXLCell, object>(cell => cell.DataType switch
{
XLDataType.Boolean => cell.GetBoolean(),
XLDataType.Number => cell.GetDouble(),
XLDataType.DateTime => cell.GetDateTime(),
_ => cell.GetString()
})
.ToList())
.ToList() ?? new XLTBL();
}
(nicht getestet, aber so sollte es eigentlich gehen)
LG
René
Dabei stelle ich fest, dass der Filter dafür gar nicht gebraucht wird.
Lieben Dank! Ich denke, ich bin selbst durcheinander gekommen, nach einer langen Nacht.
Ich habe zwar die Fehler in der Response gesehen, nicht jedoch im Log. Nun habe ich mir das ganze genauer angeschaut und im Program.cs nach dem Build() folgenden Code eingefügt:
app.UseExceptionHandler(errorApp =>
{
errorApp.Run(async context =>
{
var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.ContentType = "application/json";
// Exception-Details abrufen.
var exceptionHandlerFeature = context.Features.Get<IExceptionHandlerFeature>();
if (exceptionHandlerFeature?.Error is Exception exception)
{
// Fehler mit Stacktrace nur ins Log schreiben. "context.TraceIdentifier" hilft bei der Fehlerverfolgung im Log.
logger.LogError(exception, "Fehler {TraceId}: {ErrorMessage}", context.TraceIdentifier, exception.Message);
}
else
{
// Allgemeiner Fehler ohne Exception. "context.TraceIdentifier" hilft bei der Fehlerverfolgung im Log.
logger.LogError($"Unbekannter Fehler {context.TraceIdentifier}");
}
var errorDetails = new
{
error = "Ein interner Serverfehler ist aufgetreten.",
traceId = context.TraceIdentifier // Hilft bei der Fehlerverfolgung im Log.
};
await context.Response.WriteAsJsonAsync(errorDetails);
});
});
Nun scheint es jetzt zu funktionieren, auch wenn Müll frei Haus geliefert wird.
Die Middleware sorgt dafür, dass alle Fehler, die während einer HTTP-Anfrage passieren können, abgefangen werden. Dabei wird der Fehler ausführlich im Log gespeichert. An den Client wird nicht zu viel geschickt. Der bekommt nur eine allgemeine Fehlermeldung, allerdings mit der TraceId – Diese hilft, den Fehler im Log später nachzuvollziehen. Im Log steht, was genau schiefgelaufen ist, auch mit dem Stacktrace.
Nochmals lieben Dank und herzliche Grüße!
René
Hallo!
Ich brauche Hilfe von den Experten. Folgendes Problem: Ich baue eine API mit ASP.NET Core 8, die mit JSON-Strings gefüttert wird. Diese API empfängt Daten von einem Möbelkonfigurator, der noch fertig entwickelt werden muss. Dadurch gestaltet sich alles seitens der Firma, die den Konfigurator entwickelt, etwas zu dynamisch.
An sich ist die API fertig und betriebsbereit. Allerdings möchte die andere Softwarefirma mehr Infos, wenn sie die API testen und dabei auf Probleme stoßen. Warum sie das nicht einfach mit Postman machen, weiß ich nicht.
Auf jeden Fall lasse ich ein Log mit Serilog laufen. Die Logausgabe erfolgt in einer Datei mit einem Zeitstempel im Dateinamen. Jeden Tag wird eine neue Logdatei begonnen und es werden maximal 90 vorgehalten. Diese Logs protokollieren ziemlich alles und zwar sehr ausführlich. Auch Fehler werden darin protokolliert. Deswegen habe ich der anderen Softwarefirma einen lesenden WebDAV-Zugang zu diesen Protokollen ermöglicht, damit sie sich darin austoben können. Bis hierhin alles gut.
Nun gibt es aber einige Fehler, die bereits vor der Deserialisierung auftreten. Zum Beispiel schicken sie Daten, in denen u. a. eine GUID enthalten ist, die weder leer noch null sein darf, und sie finden nichts besseres, als null zu schicken. An dieser Stelle tritt ein Fehler auf, den ich aber bisher nicht abfangen konnte. Der Fehler wird kurz wie folgt protokolliert:
2025-01-29 07:20:58.650 +01:00 [DBG] JSON input formatter threw an exception: The JSON value could not be converted to System.Guid. Path: $.retailerId | LineNumber: 17 | BytePositionInLine: 22.
Es ist mir klar, was da passiert: retailerId kann nicht in eine GUID konvertiert werden, und man muss eben danach schauen, was da passiert ist.
Ich habe u. a. einen globalen Filter:
public class GlobalExceptionFilter : IExceptionFilter
{
private readonly ILogger<GlobalExceptionFilter> _logger;
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
{
_logger = logger;
}
public void OnException(ExceptionContext context)
{
_logger.LogError(context.Exception, $"Eine Ausnahme ist aufgetreten: {context.Exception.Message}");
// ...
// Setze das Ergebnis auf einen generischen Fehler
context.Result = new ObjectResult(new { error = "Ein Fehler ist aufgetreten. Bitte überprüfen Sie die Protokolle für weitere Details." })
{
StatusCode = 500
};
context.ExceptionHandled = true;
}
}
Zusätzlich einen benutzerdefinierten Action-Filter, der verwendet wird, um Validierungsfehler im ModelState zu überprüfen und zu protokollieren:
public class CustomModelStateInvalidFilter : IActionFilter
{
public int Order => -2100;
private readonly ILogger<CustomModelStateInvalidFilter> _logger;
public CustomModelStateInvalidFilter(ILogger<CustomModelStateInvalidFilter> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("CustomModelStateInvalidFilter wurde ausgeführt.");
if (!context.ModelState.IsValid)
{
// ...
context.Result = new BadRequestObjectResult(problemDetails);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Keine Aktion erforderlich nach der Ausführung
}
}
Diese Filter werden im Program.cs hinzugefügt:
builder.Services.AddControllers(options =>
{
options.Filters.Add<CustomModelStateInvalidFilter>();
options.Filters.Add<GlobalExceptionFilter>();
options.Filters.Add<ModelStateLoggerFilter>();
})
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
options.JsonSerializerOptions.WriteIndented = true;
});
Dann schließlich eine benutzerdefinierte Middleware, die Fehler bei der JSON-Deserialisierung sowie allgemeine Fehler abfangen und eine Fehlerantwort zurückgeben sollte:
namespace okpm.Middleware;
public class JsonExceptionHandlingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<JsonExceptionHandlingMiddleware> _logger;
public JsonExceptionHandlingMiddleware(RequestDelegate next, ILogger<JsonExceptionHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (JsonException ex)
{
_logger.LogError(ex, $"Fehler beim JSON-Parsing: {ex.Message}");
await HandleErrorAsync(context, ex, "Ungültige JSON-Daten.");
}
catch (InvalidOperationException ex) when (ex.Message.Contains("JSON input formatter"))
{
_logger.LogError(ex, $"Fehler beim JSON-Parsing: {ex.Message}");
await HandleErrorAsync(context, ex, "Fehler beim Verarbeiten der Anfrage.");
}
catch (Exception ex)
{
_logger.LogError(ex, $"Unbehandelter Fehler: {ex.Message}");
await HandleErrorAsync(context, ex, "Unbekannter Fehler.");
}
}
private Task HandleErrorAsync(HttpContext context, Exception ex, string message)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.ContentType = "application/json";
var response = new
{
message,
error = ex.Message
};
return context.Response.WriteAsync(JsonSerializer.Serialize(response));
}
}
Eingebunden wird sie im Program.cs:
app.UseMiddleware<Middleware.JsonExceptionHandlingMiddleware>();
Aber egal, was ich mache, ich kann so eine Ausnahme, die bei einem Problem beim Parsen von JSON-Daten auftritt (z. B. ungültiges JSON: retailerId = null), nicht abfangen.
Schlimm ist es nicht, denn ASP.NET Core protokolliert mir den Fehler:
2025-01-29 07:20:58.650 +01:00 [DBG] JSON input formatter threw an exception: The JSON value could not be converted to System.Guid. Path: $.retailerId | LineNumber: 17 | BytePositionInLine: 22.
Allerdings dachte ich mir, ich kann hier eingreifen und selbst die Fehlermeldung etwas umfangreicher machen und sie aufhübschen. Bisher aber erfolglos.
Hat mir jemand einen Tipp?
Danke im Voraus und liebe Grüße
René
Ja, danke, ich kenne sie. Mir geht es dabei um diese Position:
Visual Studio Professional-Abonnements (ohne monatliche Azure-Gutschrift)
Darüber hinaus weiter unten zu lesen:
Diese Tabellen dienen nur allgemeinen Informationszwecken als allgemeine Übersicht über Microsoft-Partnervorteilepakete. Die hierin aufgeführten Programminformationen können sich ändern und sollten nicht als Angebot, Bestätigung, Garantie, Verpflichtung oder sonstige Art von Darstellung von Microsoft interpretiert werden. Vollständige Details und Anforderungen sind festgelegt und unterliegen den anwendbaren Programmberatern und Partnervereinbarungen.
Also es kann sich schon das eine oder andere ändern.
Vielen Dank!
Ich habe diesen Thread vor kurzem entdeckt und mich direkt hier eingeklinkt. Wie bereits erwähnt, finde ich leider keine Informationen über den genauen Leistungsumfang der enthaltenen "Visual Studio Professional Subscription". Daher warte ich nun auf den Rückruf vom Microsoft-Support und hoffe auf eine Klärung.
Sollte ich neue Informationen erhalten, werde ich diese hier posten.
Hallo zusammen!
Ich hoffe, hier kann mir jemand mit Informationen zum Action Pack und seinem möglichen Nachfolger weiterhelfen.
Seit vielen Jahren nutze ich die "Visual Studio Professional Subscription" als Teil des Action Packs. Leider wird das Action Pack ab Januar 2025 nicht mehr angeboten. Im Action Pack sind Lizenzen für verschiedene Microsoft-Produkte enthalten, darunter Server- und Client-Betriebssysteme sowie SQL-Server, die ich als Entwickler gerne für Testzwecke nutze.
Jetzt gibt es das Produkt "Partner Success Core", das ebenfalls eine "Visual Studio Professional Subscription" umfasst. Was ich jedoch noch nicht herausfinden konnte, ist, ob diese VS-Subscription auch die Lizenzen für Microsoft-Server- und Client-Betriebssysteme sowie für SQL-Server oder andere bisher enthaltene Software beinhaltet.
Falls dies nicht der Fall sein sollte, welche einigermaßen kostengünstigen Alternativen gibt es, um an diese Ressourcen zu gelangen?
Seit gestern versuche ich, einen verlässlichen Ansprechpartner bei Microsoft zu erreichen. Ich habe bereits eine Supportanfrage eingereicht, aber bisher noch keinen Rückruf erhalten. Daher wollte ich dieses Thema hier zur Diskussion stellen – möglicherweise betrifft es auch andere, die wie ich das Action Pack nutzen.
Vielen Dank im Voraus für eure Unterstützung!
Viele Grüße
René