From 759b60889efe350d867dd87f0b37dae1a23bd64b Mon Sep 17 00:00:00 2001 From: TekH Date: Sun, 31 May 2026 11:19:03 +0200 Subject: [PATCH] Add AnnotationDto and AnnotationService for API integration Added `AnnotationDto` to represent signature annotation positions, including properties for ID, page, and coordinates, with detailed documentation on coordinate systems. Implemented `AnnotationService` to fetch annotation data from an API endpoint, using `HttpClient` and supporting cancellation tokens. Registered `AnnotationService` in the DI container to enable its use across the application. Provided development setup details for working with fake data via YARP-proxied routes. --- .../Models/AnnotationDto.cs | 29 +++++++++++++++++ EnvelopeGenerator.ReceiverUI/Program.cs | 1 + .../Services/AnnotationService.cs | 32 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 EnvelopeGenerator.ReceiverUI/Models/AnnotationDto.cs create mode 100644 EnvelopeGenerator.ReceiverUI/Services/AnnotationService.cs diff --git a/EnvelopeGenerator.ReceiverUI/Models/AnnotationDto.cs b/EnvelopeGenerator.ReceiverUI/Models/AnnotationDto.cs new file mode 100644 index 00000000..642cc6a9 --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/Models/AnnotationDto.cs @@ -0,0 +1,29 @@ +namespace EnvelopeGenerator.ReceiverUI.Models; + +/// +/// Represents a pre-assigned signature annotation position on a specific page. +///

+/// Coordinate unit (X, Y): Hundredths of an inch (1/100 inch ? 2.83 PDF points), +/// origin at the top-left corner of the page, both axes increase downward/rightward. +/// This matches the DevExpress XtraReports coordinate system (). +///

+/// Difference from PSPDFKit: Same origin (top-left) and direction, but PSPDFKit uses PDF points (1/72 inch). +/// Convert: xDX = xPsPdf * (100.0 / 72.0) +///
+/// Difference from GDPicture: GDPicture uses PDF points with bottom-left origin (PDF standard); Y is flipped. +/// Convert: yDX = (pageHeightPt - yGD - elemHeightPt) * (100.0 / 72.0) +///
+public record AnnotationDto +{ + /// Unique identifier of the annotation. + public long Id { get; init; } + + /// 1-based page number within the document. + public int? Page { get; init; } + + /// Horizontal position in hundredths of an inch from the left edge of the page. + public double? X { get; init; } + + /// Vertical position in hundredths of an inch from the top edge of the page. + public double? Y { get; init; } +} diff --git a/EnvelopeGenerator.ReceiverUI/Program.cs b/EnvelopeGenerator.ReceiverUI/Program.cs index 00b87dd5..2e54da86 100644 --- a/EnvelopeGenerator.ReceiverUI/Program.cs +++ b/EnvelopeGenerator.ReceiverUI/Program.cs @@ -16,6 +16,7 @@ builder.Services.Configure(opts => builder.Configuration.GetSection(ApiOptions.SectionName).Bind(opts)); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddDevExpressWebAssemblyBlazorReportViewer(); builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer(); diff --git a/EnvelopeGenerator.ReceiverUI/Services/AnnotationService.cs b/EnvelopeGenerator.ReceiverUI/Services/AnnotationService.cs new file mode 100644 index 00000000..5fde8210 --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/Services/AnnotationService.cs @@ -0,0 +1,32 @@ +using System.Net.Http.Json; +using System.Text.Json; +using EnvelopeGenerator.ReceiverUI.Models; +using EnvelopeGenerator.ReceiverUI.Options; +using Microsoft.Extensions.Options; + +namespace EnvelopeGenerator.ReceiverUI.Services; + +/// +/// Retrieves annotation positions from the API. +/// The URL is composed as {BaseUrl}/api/Annotation/{envelopeKey}. +/// During development, BaseUrl is empty so the request resolves to the +/// YARP-proxied route on the same origin, which currently serves +/// fake-data/annotations.json. To switch to real data, update the +/// YARP route in yarp.json — no code change required. +/// +public class AnnotationService(HttpClient http, IOptions apiOptions) +{ + private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web); + + public async Task> GetAnnotationsAsync(string envelopeKey, CancellationToken cancel = default) + { + var url = $"{apiOptions.Value.BaseUrl}/api/Annotation/{Uri.EscapeDataString(envelopeKey)}"; + var response = await http.GetAsync(url, cancel); + + if (!response.IsSuccessStatusCode) + return []; + + var result = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancel); + return result ?? []; + } +}