Files
EnvelopeGenerator/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/DevSignatureInkController.cs
TekH f1c8e223dd Refactor ComposeSignaturePreviewPng to return Stream
Updated the `ComposeSignaturePreviewPng` method in the `IPdfPageImageRenderer` interface to return a `Stream` instead of a `byte[]` for improved memory efficiency when handling large image data.

- Added `ComposeSignaturePreviewPngBytes` extension method to convert the `Stream` to a `byte[]` for backward compatibility.
- Updated `PdfPageImageRenderer` to return a `Stream` and adjusted its internal logic to reset the `MemoryStream` position before returning.
- Modified `DevSignatureInkController` to use the `Stream`-based implementation for HTTP responses.
- Made minor formatting adjustments in `PdfPageImageRenderer`.

These changes optimize memory usage while maintaining compatibility with existing workflows that require `byte[]`.
2026-10-19 14:49:48 +02:00

100 lines
3.7 KiB
C#

using DigitalData.Core.Abstraction.Application.Repository;
using DevExpress.Drawing;
using DevExpress.Pdf;
using EnvelopeGenerator.Application.Common.Dto;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Domain.Interfaces;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using EnvelopeGenerator.Application.Common.Interfaces.Doc;
namespace EnvelopeGenerator.Server.Controllers;
/// <summary>
/// Temporary development endpoint to download stored signature ink image by element id.
/// </summary>
[ApiController]
[AllowAnonymous]
[Route("api/dev/signature-ink")]
public class DevSignatureInkController(
IRepository<DocReceiverElement> repository,
IWebHostEnvironment environment,
IConfiguration configuration,
IPdfPageImageRenderer renderer) : ControllerBase
{
/// <summary>
/// Downloads the signature image stored in INK for the given DocumentReceiverElement id.
/// </summary>
[HttpGet("{id:int}")]
public async Task<IActionResult> DownloadInk([FromRoute] int id, CancellationToken cancel)
{
if (!IsEndpointEnabled())
return NotFound();
var element = await repository.Query
.AsNoTracking()
.Where(e => e.Id == id)
.Select(e => new DocReceiverElementDto
{
Id = e.Id,
Ink = e.Ink
})
.SingleOrDefaultAsync(cancel);
if (element is null)
return NotFound($"DocumentReceiverElement not found for id={id}.");
if (element.Ink is not { Length: > 0 })
return NotFound($"No signature image (Ink) found for id={id}.");
var image = element.ToImage();
return File(image.Bytes, image.ContentType, $"signature-ink-{id}{image.FileExtension}");
}
/// <summary>
/// Downloads a composed signature image (background + ink + signer metadata) for the given element id.
/// </summary>
[HttpGet("{id:int}/composed")]
public async Task<IActionResult> DownloadComposedInk([FromRoute] int id, CancellationToken cancel)
{
if (!IsEndpointEnabled())
return NotFound();
var element = await repository.Query
.AsNoTracking()
.Where(e => e.Id == id)
.Select(e => new DocReceiverElementDto
{
Id = e.Id,
Ink = e.Ink,
FullName = e.FullName,
Position = e.Position,
Place = e.Place,
ChangedWhen = e.ChangedWhen
})
.SingleOrDefaultAsync(cancel);
if (element is null)
return NotFound($"DocumentReceiverElement not found for id={id}.");
if (element.Ink is not { Length: > 0 })
return NotFound($"No signature image (Ink) found for id={id}.");
if (element.ChangedWhen is null)
return Conflict($"DocumentReceiverElement id={id} has null ChangedWhen. Element is not signed yet or data integrity is broken.");
if (string.IsNullOrWhiteSpace(element.FullName) || string.IsNullOrWhiteSpace(element.Place))
return Conflict($"DocumentReceiverElement id={id} is missing signer metadata (FullName or Place).");
if (!renderer.IsSupportedImageFormat(element.Ink!))
return StatusCode(StatusCodes.Status415UnsupportedMediaType);
var composedStream = renderer.ComposeSignaturePreviewPng(element);
return File(composedStream, "image/png", $"signature-composed-{id}.png");
}
private bool IsEndpointEnabled()
=> environment.IsDevelopment() || configuration.GetValue<bool>("DevEndpoints:EnableSignatureInkInProduction");
}