feat: Add specialized client implementations for PDF operations
This commit is contained in:
179
DocumentService.Client/Clients/BaseDocumentClient.cs
Normal file
179
DocumentService.Client/Clients/BaseDocumentClient.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
124
DocumentService.Client/Clients/PdfAttachmentClient.cs
Normal file
124
DocumentService.Client/Clients/PdfAttachmentClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
48
DocumentService.Client/Clients/PdfConversionClient.cs
Normal file
48
DocumentService.Client/Clients/PdfConversionClient.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
161
DocumentService.Client/Clients/PdfOperationsClient.cs
Normal file
161
DocumentService.Client/Clients/PdfOperationsClient.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
68
DocumentService.Client/Clients/PdfValidationClient.cs
Normal file
68
DocumentService.Client/Clients/PdfValidationClient.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
44
DocumentService.Client/Clients/SwissQrCodeClient.cs
Normal file
44
DocumentService.Client/Clients/SwissQrCodeClient.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
87
DocumentService.Client/Clients/ZugferdClient.cs
Normal file
87
DocumentService.Client/Clients/ZugferdClient.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user