feat: Add specialized client implementations for PDF operations

This commit is contained in:
2026-08-11 10:29:24 +02:00
parent 14250f0b4b
commit 058cb9327d
7 changed files with 711 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
namespace DocumentService.Client.Clients;
/// <summary>
/// Base class for all DocumentService HTTP clients.
/// Provides common HTTP operations and error handling.
/// </summary>
/// <remarks>
///
/// </remarks>
/// <param name="HttpClient"></param>
/// <param name="logger"></param>
/// <exception cref="ArgumentNullException"></exception>
public abstract class BaseDocumentClient(HttpClient HttpClient, ILogger logger)
{
/// <summary>
///
/// </summary>
protected readonly ILogger Logger = logger ?? throw new ArgumentNullException(nameof(logger));
/// <summary>
/// Sends a multipart/form-data POST with a single file and deserializes the JSON response.
/// </summary>
protected async Task<TResponse?> SendMultipartAsync<TResponse>(
string endpoint,
Stream fileStream,
string fileName = "file.pdf",
CancellationToken cancellationToken = default)
{
using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
content.Add(streamContent, "file", fileName);
var response = await HttpClient.PostAsync(endpoint, content, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken);
}
/// <summary>
/// Sends a multipart/form-data POST with a single file and returns the response as a stream.
/// </summary>
protected async Task<Stream> SendMultipartForStreamAsync(
string endpoint,
Stream fileStream,
string fileName = "file.pdf",
CancellationToken cancellationToken = default)
{
using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
content.Add(streamContent, "file", fileName);
var response = await HttpClient.PostAsync(endpoint, content, cancellationToken);
response.EnsureSuccessStatusCode();
#if NETFRAMEWORK
return await response.Content.ReadAsStreamAsync();
#else
return await response.Content.ReadAsStreamAsync(cancellationToken);
#endif
}
/// <summary>
/// Sends a multipart/form-data POST with a single file and returns the raw byte array.
/// </summary>
protected async Task<byte[]> SendMultipartForBinaryAsync(
string endpoint,
Stream fileStream,
string fileName = "file.pdf",
CancellationToken cancellationToken = default)
{
using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
content.Add(streamContent, "file", fileName);
var response = await HttpClient.PostAsync(endpoint, content, cancellationToken);
response.EnsureSuccessStatusCode();
#if NETFRAMEWORK
return await response.Content.ReadAsByteArrayAsync();
#else
return await response.Content.ReadAsByteArrayAsync(cancellationToken);
#endif
}
/// <summary>
/// Sends a caller-built <see cref="MultipartFormDataContent"/> and returns the response as a stream.
/// Use this overload when the multipart body contains more than one file (e.g., merge).
/// </summary>
protected async Task<Stream> SendMultipartContentForStreamAsync(
string endpoint,
MultipartFormDataContent content,
CancellationToken cancellationToken = default)
{
var response = await HttpClient.PostAsync(endpoint, content, cancellationToken);
response.EnsureSuccessStatusCode();
#if NETFRAMEWORK
return await response.Content.ReadAsStreamAsync();
#else
return await response.Content.ReadAsStreamAsync(cancellationToken);
#endif
}
/// <summary>
/// Serializes <paramref name="request"/> as JSON, POSTs it, and deserializes the response.
/// </summary>
protected async Task<TResponse?> SendJsonAsync<TRequest, TResponse>(
string endpoint,
TRequest request,
CancellationToken cancellationToken = default)
{
var response = await HttpClient.PostAsJsonAsync(endpoint, request, cancellationToken);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken);
}
/// <summary>
/// Serializes <paramref name="request"/> as JSON, POSTs it, and returns the response as a stream.
/// </summary>
protected async Task<Stream> SendJsonForStreamAsync<TRequest>(
string endpoint,
TRequest request,
CancellationToken cancellationToken = default)
{
var response = await HttpClient.PostAsJsonAsync(endpoint, request, cancellationToken);
response.EnsureSuccessStatusCode();
#if NETFRAMEWORK
return await response.Content.ReadAsStreamAsync();
#else
return await response.Content.ReadAsStreamAsync(cancellationToken);
#endif
}
/// <summary>
/// Serializes <paramref name="request"/> as JSON, POSTs it, and returns the raw byte array.
/// </summary>
protected async Task<byte[]> SendJsonForBinaryAsync<TRequest>(
string endpoint,
TRequest request,
CancellationToken cancellationToken = default)
{
var response = await HttpClient.PostAsJsonAsync(endpoint, request, cancellationToken);
response.EnsureSuccessStatusCode();
#if NETFRAMEWORK
return await response.Content.ReadAsByteArrayAsync();
#else
return await response.Content.ReadAsByteArrayAsync(cancellationToken);
#endif
}
/// <summary>Converts a byte array to a Base64 string.</summary>
protected static string ToBase64(byte[] bytes) => Convert.ToBase64String(bytes);
/// <summary>Reads a stream fully into a byte array.</summary>
protected static async Task<byte[]> StreamToBytesAsync(Stream stream, CancellationToken cancellationToken = default)
{
if (stream is MemoryStream ms)
return ms.ToArray();
using var memoryStream = new MemoryStream();
#if NET8_0
await stream.CopyToAsync(memoryStream, cancellationToken);
#else
await stream.CopyToAsync(memoryStream);
#endif
return memoryStream.ToArray();
}
}

View File

@@ -0,0 +1,124 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.IO.Compression;
using System.Net.Http;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of PDF attachment client.
/// </summary>
public class PdfAttachmentClient(HttpClient httpClient, ILogger<PdfAttachmentClient> logger)
: BaseDocumentClient(httpClient, logger), IPdfAttachmentClient
{
/// <inheritdoc />
public async Task<AttachmentCheckResult> CheckAttachmentsAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Checking PDF attachments from stream (multipart)");
var result = await SendMultipartAsync<AttachmentCheckResult>(
"/api/pdf/attachments/check",
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<AttachmentCheckResult> CheckAttachmentsAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Checking PDF attachments from byte array (Base64 JSON)");
var request = new CheckPdfAttachmentsRequest { Base64Pdf = ToBase64(pdfBytes) };
var result = await SendJsonAsync<CheckPdfAttachmentsRequest, AttachmentCheckResult>(
"/api/pdf/attachments/check",
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<Dictionary<string, Stream>> ExtractAttachmentsAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Extracting PDF attachments from stream (multipart)");
var zipStream = await SendMultipartForStreamAsync(
"/api/pdf/attachments/extract",
pdfStream,
"document.pdf",
cancellationToken);
return UnzipToStreams(zipStream);
}
/// <inheritdoc />
public async Task<Dictionary<string, Stream>> ExtractAttachmentsAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Extracting PDF attachments from byte array (Base64 JSON)");
var request = new ExtractPdfAttachmentsRequest { Base64Pdf = ToBase64(pdfBytes) };
var zipStream = await SendJsonForStreamAsync(
"/api/pdf/attachments/extract",
request,
cancellationToken);
return UnzipToStreams(zipStream);
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> AddAttachmentsAsync(Stream pdfStream, List<AttachmentRequestDto> attachments, CancellationToken cancellationToken = default)
{
Logger.LogWarning("AddAttachments endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint /api/pdf/attachments/add is not implemented yet");
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> AddAttachmentsAsync(byte[] pdfBytes, List<AttachmentRequestDto> attachments, CancellationToken cancellationToken = default)
{
Logger.LogWarning("AddAttachments endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint /api/pdf/attachments/add is not implemented yet");
}
// ?????????????????????????????????????????????????????????????????????????
// Private helpers
// ?????????????????????????????????????????????????????????????????????????
/// <summary>
/// Reads a ZIP stream and returns a dictionary mapping each entry's full name
/// to an in-memory <see cref="MemoryStream"/> containing the decompressed bytes.
/// The caller owns the returned streams and is responsible for disposing them.
/// </summary>
private static Dictionary<string, Stream> UnzipToStreams(Stream zipStream)
{
var result = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase);
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen: false);
foreach (var entry in archive.Entries)
{
// Skip directory entries (name ends with '/')
if (string.IsNullOrEmpty(entry.Name))
continue;
var ms = new MemoryStream((int)entry.Length);
using (var entryStream = entry.Open())
{
entryStream.CopyTo(ms);
}
ms.Position = 0;
result[entry.FullName] = ms;
}
return result;
}
}

View File

@@ -0,0 +1,48 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.Net.Http;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of PDF conversion client (PDF ? PDF/A).
/// </summary>
/// <remarks>
/// All methods throw <see cref="NotImplementedException"/> because the corresponding
/// API endpoints are not yet implemented on the server side.
/// </remarks>
public class PdfConversionClient(HttpClient httpClient, ILogger<PdfConversionClient> logger) : BaseDocumentClient(httpClient, logger), IPdfConversionClient
{
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> ConvertToPdfAAsync(Stream pdfStream, string pdfALevel = "PDF/A-3b", CancellationToken cancellationToken = default)
{
Logger.LogWarning("ConvertToPdfA endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint POST /api/pdf/conversion/to-pdfa is not implemented yet");
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> ConvertToPdfAAsync(byte[] pdfBytes, string pdfALevel = "PDF/A-3b", CancellationToken cancellationToken = default)
{
Logger.LogWarning("ConvertToPdfA endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint POST /api/pdf/conversion/to-pdfa is not implemented yet");
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> ConvertFromPdfAAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
Logger.LogWarning("ConvertFromPdfA endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint POST /api/pdf/conversion/from-pdfa is not implemented yet");
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> ConvertFromPdfAAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
Logger.LogWarning("ConvertFromPdfA endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint POST /api/pdf/conversion/from-pdfa is not implemented yet");
}
}

View File

@@ -0,0 +1,161 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Net.Http.Headers;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of PDF operations client (merge, annotate, stamp).
/// </summary>
public class PdfOperationsClient(HttpClient httpClient, ILogger<PdfOperationsClient> logger) : BaseDocumentClient(httpClient, logger), IPdfOperationsClient
{
// ==================== MERGE OPERATIONS ====================
/// <inheritdoc />
public async Task<Stream> MergeAsync(IEnumerable<Stream> pdfStreams, List<string?>? pageRanges = null, CancellationToken cancellationToken = default)
{
using var content = new MultipartFormDataContent();
foreach (var stream in pdfStreams)
{
var streamContent = new StreamContent(stream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
content.Add(streamContent, "files", $"file_{Guid.NewGuid()}.pdf");
}
return await SendMultipartContentForStreamAsync("/api/pdf/operations/merge", content, cancellationToken);
}
/// <inheritdoc />
public async Task<Stream> MergeAsync(IEnumerable<byte[]> pdfByteArrays, List<string?>? pageRanges = null, CancellationToken cancellationToken = default)
{
var request = new MergePdfsBase64Request
{
Base64Pdfs = [.. pdfByteArrays.Select(ToBase64)],
PageRanges = pageRanges
};
return await SendJsonForStreamAsync(
"/api/pdf/operations/merge",
request,
cancellationToken);
}
// ==================== ANNOTATION OPERATIONS ====================
/// <inheritdoc />
public async Task<Stream> AnnotateAsync(Stream pdfStream, AddAnnotationBase64Request request, CancellationToken cancellationToken = default)
{
// For multipart, we need to send form data with all annotation parameters
using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(pdfStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
content.Add(streamContent, "File", "document.pdf");
// Add annotation parameters as form fields
content.Add(new StringContent(request.AnnotationType.ToString()), "AnnotationType");
content.Add(new StringContent(request.PageNumber.ToString()), "PageNumber");
content.Add(new StringContent(request.X1.ToString()), "X1");
content.Add(new StringContent(request.Y1.ToString()), "Y1");
if (request.X2.HasValue)
content.Add(new StringContent(request.X2.Value.ToString()), "X2");
if (request.Y2.HasValue)
content.Add(new StringContent(request.Y2.Value.ToString()), "Y2");
if (request.Width.HasValue)
content.Add(new StringContent(request.Width.Value.ToString()), "Width");
if (request.Height.HasValue)
content.Add(new StringContent(request.Height.Value.ToString()), "Height");
if (!string.IsNullOrEmpty(request.Content))
content.Add(new StringContent(request.Content), "Content");
if (!string.IsNullOrEmpty(request.Author))
content.Add(new StringContent(request.Author), "Author");
if (!string.IsNullOrEmpty(request.Color))
content.Add(new StringContent(request.Color), "Color");
if (request.TextMarkupStyle.HasValue)
content.Add(new StringContent(request.TextMarkupStyle.Value.ToString()), "TextMarkupStyle");
content.Add(new StringContent(request.Origin.ToString()), "Origin");
return await SendMultipartContentForStreamAsync("/api/pdf/operations/annotate", content, cancellationToken);
}
/// <inheritdoc />
public async Task<Stream> AnnotateAsync(byte[] pdfBytes, AddAnnotationBase64Request request, CancellationToken cancellationToken = default)
{
var requestWithPdf = request with { Base64Pdf = ToBase64(pdfBytes) };
return await SendJsonForStreamAsync(
"/api/pdf/operations/annotate",
requestWithPdf,
cancellationToken);
}
// ==================== STAMP OPERATIONS ====================
/// <inheritdoc />
public async Task<Stream> StampAsync(Stream pdfStream, AddStampBase64Request request, CancellationToken cancellationToken = default)
{
// For multipart, we need to send form data with all stamp parameters
using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(pdfStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
content.Add(streamContent, "File", "document.pdf");
// Add stamp parameters as form fields
content.Add(new StringContent(request.StampType.ToString()), "StampType");
content.Add(new StringContent(request.X.ToString()), "X");
content.Add(new StringContent(request.Y.ToString()), "Y");
if (request.PageNumbers != null && request.PageNumbers.Length > 0)
{
foreach (var pageNum in request.PageNumbers)
{
content.Add(new StringContent(pageNum.ToString()), "PageNumbers");
}
}
if (request.Width.HasValue)
content.Add(new StringContent(request.Width.Value.ToString()), "Width");
if (request.Height.HasValue)
content.Add(new StringContent(request.Height.Value.ToString()), "Height");
content.Add(new StringContent(request.Origin.ToString()), "Origin");
if (!string.IsNullOrEmpty(request.Text))
content.Add(new StringContent(request.Text), "Text");
if (!string.IsNullOrEmpty(request.FontName))
content.Add(new StringContent(request.FontName), "FontName");
if (request.FontSize.HasValue)
content.Add(new StringContent(request.FontSize.Value.ToString()), "FontSize");
if (!string.IsNullOrEmpty(request.Color))
content.Add(new StringContent(request.Color), "Color");
if (request.Opacity.HasValue)
content.Add(new StringContent(request.Opacity.Value.ToString()), "Opacity");
if (request.Rotation.HasValue)
content.Add(new StringContent(request.Rotation.Value.ToString()), "Rotation");
content.Add(new StringContent(request.Placement.ToString()), "Placement");
if (request.PredefinedType.HasValue)
content.Add(new StringContent(request.PredefinedType.Value.ToString()), "PredefinedType");
return await SendMultipartContentForStreamAsync("/api/pdf/operations/stamp", content, cancellationToken);
}
/// <inheritdoc />
public async Task<Stream> StampAsync(byte[] pdfBytes, AddStampBase64Request request, CancellationToken cancellationToken = default)
{
var requestWithPdf = request with { Base64Pdf = ToBase64(pdfBytes) };
return await SendJsonForStreamAsync(
"/api/pdf/operations/stamp",
requestWithPdf,
cancellationToken);
}
}

View File

@@ -0,0 +1,68 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.Net.Http;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of PDF validation client.
/// </summary>
public class PdfValidationClient(HttpClient httpClient, ILogger<PdfValidationClient> logger) : BaseDocumentClient(httpClient, logger), IPdfValidationClient
{
/// <inheritdoc />
public async Task<PdfValidationResult> ValidatePdfAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
var result = await SendMultipartAsync<PdfValidationResult>(
"/api/pdf/validation/validate",
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<PdfValidationResult> ValidatePdfAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
var request = new ValidatePdfBase64Request
{
Base64Pdf = ToBase64(pdfBytes)
};
var result = await SendJsonAsync<ValidatePdfBase64Request, PdfValidationResult>(
"/api/pdf/validation/validate",
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<PdfAValidationResult> ValidatePdfAAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
var result = await SendMultipartAsync<PdfAValidationResult>(
"/api/pdf/validation/validate-pdfa",
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<PdfAValidationResult> ValidatePdfAAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
var request = new ValidatePdfABase64Request
{
Base64Pdf = ToBase64(pdfBytes)
};
var result = await SendJsonAsync<ValidatePdfABase64Request, PdfAValidationResult>(
"/api/pdf/validation/validate-pdfa",
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
}

View File

@@ -0,0 +1,44 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.Net.Http;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of Swiss QR Code extraction client.
/// </summary>
public class SwissQrCodeClient(HttpClient httpClient, ILogger<SwissQrCodeClient> logger) : BaseDocumentClient(httpClient, logger), ISwissQrCodeClient
{
/// <inheritdoc />
public async Task<SwissQrCodeExtractionResult> ExtractSwissQrCodeAsync(Stream pdfStream, bool raw = false, CancellationToken cancellationToken = default)
{
var endpoint = $"/api/pdf/qr-code/extract-swiss?raw={raw}";
var result = await SendMultipartAsync<SwissQrCodeExtractionResult>(
endpoint,
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<SwissQrCodeExtractionResult> ExtractSwissQrCodeAsync(byte[] pdfBytes, bool raw = false, CancellationToken cancellationToken = default)
{
var endpoint = $"/api/pdf/qr-code/extract-swiss?raw={raw}";
var request = new ExtractSwissQrCodeBase64Request
{
Base64Pdf = ToBase64(pdfBytes)
};
var result = await SendJsonAsync<ExtractSwissQrCodeBase64Request, SwissQrCodeExtractionResult>(
endpoint,
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
}

View File

@@ -0,0 +1,87 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.Net.Http;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of ZUGFeRD client (detection and extraction).
/// </summary>
public class ZugferdClient(HttpClient httpClient, ILogger<ZugferdClient> logger) : BaseDocumentClient(httpClient, logger), IZugferdClient
{
/// <inheritdoc />
public async Task<ZugferdCheckResult> HasZugferdAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
var result = await SendMultipartAsync<ZugferdCheckResult>(
"/api/pdf/zugferd/has-zugferd",
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<ZugferdCheckResult> HasZugferdAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
var request = new HasZugferdRequest { Base64Pdf = ToBase64(pdfBytes) };
var result = await SendJsonAsync<HasZugferdRequest, ZugferdCheckResult>(
"/api/pdf/zugferd/has-zugferd",
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<Stream> ExtractZugferdAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
// asFile=true returns the XML file directly (application/xml stream)
return await SendMultipartForStreamAsync(
"/api/pdf/zugferd/extract?asFile=true",
pdfStream,
"document.pdf",
cancellationToken);
}
/// <inheritdoc />
public async Task<Stream> ExtractZugferdAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
var request = new ExtractZugferdRequest { Base64Pdf = ToBase64(pdfBytes) };
// format=file returns the XML file directly (application/xml stream)
return await SendJsonForStreamAsync(
"/api/pdf/zugferd/extract?format=file",
request,
cancellationToken);
}
/// <inheritdoc />
public async Task<ZugferdExtractionResult> ExtractZugferdAsResultAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
// asFile=false returns JSON with metadata + XML content
var result = await SendMultipartAsync<ZugferdExtractionResult>(
"/api/pdf/zugferd/extract?asFile=false",
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<ZugferdExtractionResult> ExtractZugferdAsResultAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
var request = new ExtractZugferdRequest { Base64Pdf = ToBase64(pdfBytes) };
// format=json returns JSON with metadata + XML content
var result = await SendJsonAsync<ExtractZugferdRequest, ZugferdExtractionResult>(
"/api/pdf/zugferd/extract?format=json",
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
}