Compare commits
13 Commits
27ed3689f2
...
b708343db0
| Author | SHA1 | Date | |
|---|---|---|---|
| b708343db0 | |||
| b416823f38 | |||
| 5bdc552492 | |||
| cdf34a262b | |||
| 4c84b28034 | |||
| b70f902190 | |||
| 094c87eb88 | |||
| 9b2539e378 | |||
| 0b73a90b15 | |||
| 76cfe4dc46 | |||
| c1a10cc0fa | |||
| b6b5ca52f2 | |||
| 5279731281 |
@@ -6,7 +6,6 @@ using EnvelopeGenerator.Domain.Constants;
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Threading.Channels;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.API.Controllers;
|
namespace EnvelopeGenerator.API.Controllers;
|
||||||
|
|
||||||
@@ -16,7 +15,6 @@ namespace EnvelopeGenerator.API.Controllers;
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Initializes a new instance of the <see cref="DocumentController"/> class.
|
/// Initializes a new instance of the <see cref="DocumentController"/> class.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Authorize]
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class DocumentController(IMediator mediator, IAuthorizationService authService, ILogger<DocumentController> logger) : ControllerBase, IAuthController
|
public class DocumentController(IMediator mediator, IAuthorizationService authService, ILogger<DocumentController> logger) : ControllerBase, IAuthController
|
||||||
@@ -70,20 +68,21 @@ public class DocumentController(IMediator mediator, IAuthorizationService authSe
|
|||||||
/// <param name="cancel"></param>
|
/// <param name="cancel"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("{envelopeKey}")]
|
[HttpGet("{envelopeKey}")]
|
||||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
|
||||||
public async Task<IActionResult> GetDocumentOfReceiver(string envelopeKey, CancellationToken cancel)
|
public async Task<IActionResult> GetDocumentOfReceiver(string envelopeKey, CancellationToken cancel)
|
||||||
{
|
{
|
||||||
var envelopeIdStr = User.FindFirst(EnvelopeClaimNames.EnvelopeId)?.Value;
|
var envelopeId = 2071;
|
||||||
|
|
||||||
if (!int.TryParse(envelopeIdStr, out int envelopeId))
|
//var envelopeIdStr = User.FindFirst(EnvelopeClaimNames.EnvelopeId)?.Value;
|
||||||
{
|
|
||||||
logger.LogError(
|
|
||||||
"Inner service error: Failed to parse Envelope ID from claims. Claim '{ClaimName}' had an invalid or missing value: '{ClaimValue}'.",
|
|
||||||
EnvelopeClaimNames.EnvelopeId,
|
|
||||||
envelopeIdStr ?? "null");
|
|
||||||
|
|
||||||
return StatusCode(StatusCodes.Status500InternalServerError, "Inner service error: Invalid envelope claim.");
|
//if (!int.TryParse(envelopeIdStr, out int envelopeId))
|
||||||
}
|
//{
|
||||||
|
// logger.LogError(
|
||||||
|
// "Inner service error: Failed to parse Envelope ID from claims. Claim '{ClaimName}' had an invalid or missing value: '{ClaimValue}'.",
|
||||||
|
// EnvelopeClaimNames.EnvelopeId,
|
||||||
|
// envelopeIdStr ?? "null");
|
||||||
|
|
||||||
|
// return StatusCode(StatusCodes.Status500InternalServerError, "Inner service error: Invalid envelope claim.");
|
||||||
|
//}
|
||||||
|
|
||||||
var senderDoc = await mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel);
|
var senderDoc = await mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel);
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ public class EnvelopeController : ControllerBase
|
|||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateAsync([FromBody] CreateEnvelopeCommand command)
|
public async Task<IActionResult> CreateAsync([FromBody] CreateEnvelopeCommand command)
|
||||||
{
|
{
|
||||||
var res = await _mediator.Send(command.Authorize(User.GetId()));
|
var res = await _mediator.Send(command.WithAuth(User.GetId()));
|
||||||
|
|
||||||
if (res is null)
|
if (res is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net9.0</TargetFrameworks>
|
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
@@ -34,8 +34,12 @@
|
|||||||
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
|
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
|
||||||
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
|
<PackageReference Include="itext" Version="8.0.5" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6" />
|
<PackageReference Include="itext.bouncy-castle-adapter" Version="8.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" Condition="'$(TargetFramework)' == 'net9.0'" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" Condition="'$(TargetFramework)' == 'net8.0'" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6" Condition="'$(TargetFramework)' == 'net9.0'" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" Condition="'$(TargetFramework)' == 'net8.0'" />
|
||||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
||||||
<PackageReference Include="NLog" Version="5.2.5" />
|
<PackageReference Include="NLog" Version="5.2.5" />
|
||||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
||||||
@@ -72,6 +76,7 @@
|
|||||||
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||||
|
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ try
|
|||||||
|
|
||||||
options.DocumentFilter<EnvelopeGenerator.API.Documentation.AuthProxyDocumentFilter>();
|
options.DocumentFilter<EnvelopeGenerator.API.Documentation.AuthProxyDocumentFilter>();
|
||||||
});
|
});
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
builder.Services.AddOpenApi();
|
builder.Services.AddOpenApi();
|
||||||
|
#endif
|
||||||
|
|
||||||
//Add EF Core dbcontext
|
//Add EF Core dbcontext
|
||||||
var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue<bool>("UseDbMigration");
|
var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue<bool>("UseDbMigration");
|
||||||
@@ -288,7 +290,9 @@ try
|
|||||||
|
|
||||||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||||
|
|
||||||
|
#if NET9_0_OR_GREATER
|
||||||
app.MapOpenApi();
|
app.MapOpenApi();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
|
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
|
||||||
|
|||||||
@@ -1,9 +1,33 @@
|
|||||||
{
|
{
|
||||||
"ReverseProxy": {
|
"ReverseProxy": {
|
||||||
"Routes": {
|
"Routes": {
|
||||||
"receiver-ui": {
|
"receiver-ui-receiver": {
|
||||||
"ClusterId": "receiver-ui",
|
"ClusterId": "receiver-ui",
|
||||||
"Order": 100,
|
"Order": 100,
|
||||||
|
"Match": {
|
||||||
|
"Path": "/receiver/{**catch-all}",
|
||||||
|
"Methods": [ "GET", "HEAD" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"receiver-ui-sender": {
|
||||||
|
"ClusterId": "receiver-ui",
|
||||||
|
"Order": 100,
|
||||||
|
"Match": {
|
||||||
|
"Path": "/sender/{**catch-all}",
|
||||||
|
"Methods": [ "GET", "HEAD" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"receiver-ui-envelope": {
|
||||||
|
"ClusterId": "receiver-ui",
|
||||||
|
"Order": 100,
|
||||||
|
"Match": {
|
||||||
|
"Path": "/envelope/{**catch-all}",
|
||||||
|
"Methods": [ "GET", "HEAD" ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"receiver-ui-static-assets": {
|
||||||
|
"ClusterId": "receiver-ui",
|
||||||
|
"Order": 999,
|
||||||
"Match": {
|
"Match": {
|
||||||
"Path": "{**catch-all}",
|
"Path": "{**catch-all}",
|
||||||
"Methods": [ "GET", "HEAD" ]
|
"Methods": [ "GET", "HEAD" ]
|
||||||
@@ -49,3 +73,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ public record CreateEnvelopeCommand : IRequest<EnvelopeDto?>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId"></param>
|
/// <param name="userId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool Authorize(int userId)
|
public CreateEnvelopeCommand WithAuth(int userId)
|
||||||
{
|
{
|
||||||
UserId = userId;
|
UserId = userId;
|
||||||
return true;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -24,6 +24,10 @@
|
|||||||
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DevExpress.Blazor.PdfViewer" Version="25.2.3" />
|
||||||
|
<PackageReference Include="DevExpress.Blazor.Reporting.JSBasedControls" Version="25.2.3" />
|
||||||
|
<PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" />
|
||||||
|
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.2.3" />
|
||||||
<PackageReference Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="8.3.1.2" />
|
<PackageReference Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="8.3.1.2" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.119.1" />
|
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.119.1" />
|
||||||
<PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.1" />
|
<PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.1" />
|
||||||
@@ -32,9 +36,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.11" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.11" PrivateAssets="all" />
|
||||||
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.2.3" />
|
|
||||||
<PackageReference Include="DevExpress.Blazor.Reporting.JSBasedControls" Version="25.2.3" />
|
|
||||||
<PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="Properties\PublishProfiles\" />
|
<Folder Include="Properties\PublishProfiles\" />
|
||||||
|
|||||||
8
EnvelopeGenerator.ReceiverUI/Options/ApiOptions.cs
Normal file
8
EnvelopeGenerator.ReceiverUI/Options/ApiOptions.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace EnvelopeGenerator.ReceiverUI.Options;
|
||||||
|
|
||||||
|
public class ApiOptions
|
||||||
|
{
|
||||||
|
public const string SectionName = "Api";
|
||||||
|
|
||||||
|
public string BaseUrl { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@page "/receiver"
|
@page "/receiver"
|
||||||
|
@page "/receiver/{EnvelopeKey}"
|
||||||
@using System.Drawing
|
@using System.Drawing
|
||||||
@using DevExpress.Drawing
|
@using DevExpress.Drawing
|
||||||
@using DevExpress.Utils
|
@using DevExpress.Utils
|
||||||
@@ -10,9 +11,11 @@
|
|||||||
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
|
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
|
||||||
@using XRControl = DevExpress.XtraReports.UI.XRControl
|
@using XRControl = DevExpress.XtraReports.UI.XRControl
|
||||||
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
|
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
|
||||||
@using EnvelopeGenerator.ReceiverUI.Services;
|
@using EnvelopeGenerator.ReceiverUI.Services
|
||||||
|
@using DevExpress.Blazor.Reporting
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@inject InMemoryReportStorageWebExtension ReportStorage
|
@inject InMemoryReportStorageWebExtension ReportStorage
|
||||||
|
@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService
|
||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
||||||
@@ -119,7 +122,9 @@
|
|||||||
</DxPopup>
|
</DxPopup>
|
||||||
|
|
||||||
<div class="receiver-viewer-wrapper">
|
<div class="receiver-viewer-wrapper">
|
||||||
@if(Report is not null) {
|
@if(!string.IsNullOrWhiteSpace(EnvelopeKey) && PdfBytes is { Length: > 0 }) {
|
||||||
|
<DxPdfViewer @key="ViewerKey" CssClass="w-100 h-100" DocumentContent="PdfBytes" />
|
||||||
|
} else if(Report is not null) {
|
||||||
<DxReportViewer @key="ViewerKey" @ref="reportViewer" Report="Report" RootCssClasses="w-100 h-100" />
|
<DxReportViewer @key="ViewerKey" @ref="reportViewer" Report="Report" RootCssClasses="w-100 h-100" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -127,6 +132,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
const string SignatureTabDraw = "draw";
|
const string SignatureTabDraw = "draw";
|
||||||
const string SignatureTabText = "text";
|
const string SignatureTabText = "text";
|
||||||
const string SignatureTabImage = "image";
|
const string SignatureTabImage = "image";
|
||||||
@@ -143,8 +149,13 @@
|
|||||||
("Cursive", "cursive")
|
("Cursive", "cursive")
|
||||||
};
|
};
|
||||||
|
|
||||||
DxReportViewer reportViewer;
|
[Parameter] public string? EnvelopeKey { get; set; }
|
||||||
|
|
||||||
|
DxReportViewer? reportViewer;
|
||||||
XtraReport? Report;
|
XtraReport? Report;
|
||||||
|
string PdfViewerUrl = string.Empty;
|
||||||
|
byte[]? PdfBytes;
|
||||||
|
byte[]? SignedPdfBytes;
|
||||||
bool SignatureApplied;
|
bool SignatureApplied;
|
||||||
bool SignaturePopupVisible;
|
bool SignaturePopupVisible;
|
||||||
string? SignatureValidationMessage;
|
string? SignatureValidationMessage;
|
||||||
@@ -158,9 +169,35 @@
|
|||||||
int ViewerKey;
|
int ViewerKey;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
Report = CreateReportInstance();
|
|
||||||
|
|
||||||
await Task.CompletedTask;
|
EnvelopeKey = null; // Force report generation for testing. Remove this line to enable EnvelopeKey-based loading.
|
||||||
|
if (!string.IsNullOrWhiteSpace(EnvelopeKey)) {
|
||||||
|
(PdfBytes, _) = await DocumentService.GetDocumentAsync(EnvelopeKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Report = CreateReportInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task<XtraReport?> BuildPdfReportAsync(string key) {
|
||||||
|
Console.WriteLine("BuildPdfReportAsync is invoked..");
|
||||||
|
var (pdfBytes, _) = await DocumentService.GetDocumentAsync(key);
|
||||||
|
Console.WriteLine($"[BuildPdfReport] key={key}, pdfBytes={pdfBytes?.Length ?? 0}");
|
||||||
|
|
||||||
|
if (pdfBytes is not { Length: > 0 })
|
||||||
|
return CreateReportInstance();
|
||||||
|
|
||||||
|
var report = new XtraReport();
|
||||||
|
var detail = new DevExpress.XtraReports.UI.DetailBand();
|
||||||
|
report.Bands.Add(detail);
|
||||||
|
var pdfContent = new DevExpress.XtraReports.UI.XRPdfContent { Source = pdfBytes, GenerateOwnPages = true };
|
||||||
|
detail.Controls.Add(pdfContent);
|
||||||
|
Console.WriteLine($"[BuildPdfReport] XRPdfContent added, Source length={pdfContent.Source?.Length ?? 0}");
|
||||||
|
|
||||||
|
ReportStorage.SetData(report, key);
|
||||||
|
var result = ReportStorage.TryGetReport(key, out var stored) ? stored : report;
|
||||||
|
Console.WriteLine($"[BuildPdfReport] TryGetReport success={stored is not null}, bands={result?.Bands?.Count}");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task OpenSignaturePopupAsync() {
|
async Task OpenSignaturePopupAsync() {
|
||||||
@@ -244,10 +281,8 @@
|
|||||||
|
|
||||||
PopupValidationMessage = null;
|
PopupValidationMessage = null;
|
||||||
SignatureValidationMessage = null;
|
SignatureValidationMessage = null;
|
||||||
Report = CreateSignedReportInstance(signatureDataUrl, SignerFullName.Trim(), SignerPosition.Trim(), SignaturePlace.Trim());
|
|
||||||
SignatureApplied = true;
|
SignatureApplied = true;
|
||||||
SignaturePopupVisible = false;
|
SignaturePopupVisible = false;
|
||||||
ViewerKey++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task<string?> GetActiveSignatureDataUrlAsync() {
|
async Task<string?> GetActiveSignatureDataUrlAsync() {
|
||||||
@@ -263,13 +298,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async Task ExportSignedPdfAsync() {
|
async Task ExportSignedPdfAsync() {
|
||||||
if(!SignatureApplied || Report is null) {
|
if(!SignatureApplied) {
|
||||||
SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu.";
|
SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
SignatureValidationMessage = null;
|
SignatureValidationMessage = null;
|
||||||
|
|
||||||
|
var signatureDataUrl = await GetActiveSignatureDataUrlAsync();
|
||||||
|
if(string.IsNullOrWhiteSpace(signatureDataUrl)) {
|
||||||
|
SignatureValidationMessage = "Die Unterschrift konnte nicht gelesen werden.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var signedKey = $"{EnvelopeKey}_signed";
|
||||||
|
var signedReport = new XtraReport();
|
||||||
|
var detail = new DevExpress.XtraReports.UI.DetailBand();
|
||||||
|
signedReport.Bands.Add(detail);
|
||||||
|
detail.Controls.Add(new DevExpress.XtraReports.UI.XRPdfContent { Source = PdfBytes });
|
||||||
|
ReportStorage.SetData(signedReport, signedKey);
|
||||||
|
Report = ReportStorage.TryGetReport(signedKey, out var stored) ? stored : signedReport;
|
||||||
|
ViewerKey++;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await Task.Delay(300);
|
||||||
await reportViewer.ExportToAsync(ExportFormat.Pdf);
|
await reportViewer.ExportToAsync(ExportFormat.Pdf);
|
||||||
} catch(Exception) {
|
} catch(Exception) {
|
||||||
SignatureValidationMessage = "Das signierte PDF konnte nicht exportiert werden. Bitte laden Sie die Seite neu und versuchen Sie es erneut.";
|
SignatureValidationMessage = "Das signierte PDF konnte nicht exportiert werden. Bitte laden Sie die Seite neu und versuchen Sie es erneut.";
|
||||||
@@ -283,7 +335,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
XtraReport CreateSignedReportInstance(string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) {
|
XtraReport CreateSignedReportInstance(string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) {
|
||||||
var report = CreateReportInstance();
|
var baseReportName = string.IsNullOrWhiteSpace(EnvelopeKey) ? "LargeDatasetReport" : EnvelopeKey;
|
||||||
|
var report = ReportStorage.TryGetReport(baseReportName, out var stored) ? stored : CreateReportInstance();
|
||||||
AddSignature(report, signatureDataUrl, signerFullName, signerPosition, signaturePlace);
|
AddSignature(report, signatureDataUrl, signerFullName, signerPosition, signaturePlace);
|
||||||
return report;
|
return report;
|
||||||
}
|
}
|
||||||
@@ -358,3 +411,4 @@
|
|||||||
bottomMargin.Controls.Remove(control);
|
bottomMargin.Controls.Remove(control);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
81
EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor
Normal file
81
EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
@page "/test"
|
||||||
|
@using System.Drawing
|
||||||
|
@using DevExpress.Drawing
|
||||||
|
@using DevExpress.Utils
|
||||||
|
@using DevExpress.XtraPrinting
|
||||||
|
@using DevExpress.XtraPrinting.Drawing
|
||||||
|
@using XtraReport = DevExpress.XtraReports.UI.XtraReport
|
||||||
|
@using BottomMarginBand = DevExpress.XtraReports.UI.BottomMarginBand
|
||||||
|
@using XRLabel = DevExpress.XtraReports.UI.XRLabel
|
||||||
|
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
|
||||||
|
@using XRControl = DevExpress.XtraReports.UI.XRControl
|
||||||
|
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
|
||||||
|
@using EnvelopeGenerator.ReceiverUI.Services
|
||||||
|
@using DevExpress.Blazor.Reporting
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
@inject InMemoryReportStorageWebExtension ReportStorage
|
||||||
|
@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService
|
||||||
|
@inject Microsoft.Extensions.Configuration.IConfiguration Configuration
|
||||||
|
@inject HttpClient Http
|
||||||
|
@using System;
|
||||||
|
|
||||||
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
|
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<div class="receiver-page-layout">
|
||||||
|
|
||||||
|
<div class="receiver-viewer-wrapper">
|
||||||
|
<embed class="w-100 h-50" src="/docs/Document.pdf" type="application/pdf" />
|
||||||
|
@if (PdfBytes is { Length: > 0 }) {
|
||||||
|
<DxPdfViewer DocumentContent="PdfBytes" CssClass="w-100 h-50" />
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
<div>Not found</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
const string SignatureTabDraw = "draw";
|
||||||
|
const string SignatureTabText = "text";
|
||||||
|
const string SignatureTabImage = "image";
|
||||||
|
const string DrawCanvasId = "receiver-signature-pad";
|
||||||
|
const string TypedCanvasId = "receiver-typed-signature-pad";
|
||||||
|
const string ImageInputId = "receiver-signature-image-input";
|
||||||
|
const string ImageCanvasId = "receiver-image-signature-pad";
|
||||||
|
|
||||||
|
readonly (string Text, string Value)[] TypedSignatureFonts = {
|
||||||
|
("Brush Script", "'Brush Script MT', cursive"),
|
||||||
|
("Segoe Script", "'Segoe Script', cursive"),
|
||||||
|
("Lucida Handwriting", "'Lucida Handwriting', cursive"),
|
||||||
|
("Comic Sans", "'Comic Sans MS', cursive"),
|
||||||
|
("Cursive", "cursive")
|
||||||
|
};
|
||||||
|
|
||||||
|
[Parameter] public string? EnvelopeKey { get; set; }
|
||||||
|
|
||||||
|
DxReportViewer? reportViewer;
|
||||||
|
XtraReport? Report;
|
||||||
|
string PdfViewerUrl = string.Empty;
|
||||||
|
byte[]? PdfBytes;
|
||||||
|
byte[]? SignedPdfBytes;
|
||||||
|
bool SignatureApplied;
|
||||||
|
bool SignaturePopupVisible;
|
||||||
|
string? PopupValidationMessage;
|
||||||
|
string ActiveSignatureTab = SignatureTabDraw;
|
||||||
|
string TypedSignatureText = string.Empty;
|
||||||
|
string TypedSignatureFont = "'Brush Script MT', cursive";
|
||||||
|
string SignerFullName = string.Empty;
|
||||||
|
string SignerPosition = string.Empty;
|
||||||
|
string SignaturePlace = string.Empty;
|
||||||
|
int ViewerKey;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync() {
|
||||||
|
PdfBytes = await Http.GetByteArrayAsync("/docs/Document.pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Components.Web;
|
|||||||
using EnvelopeGenerator.ReceiverUI;
|
using EnvelopeGenerator.ReceiverUI;
|
||||||
using DevExpress.DataAccess.Web;
|
using DevExpress.DataAccess.Web;
|
||||||
using EnvelopeGenerator.ReceiverUI.Services;
|
using EnvelopeGenerator.ReceiverUI.Services;
|
||||||
|
using EnvelopeGenerator.ReceiverUI.Options;
|
||||||
using DevExpress.XtraReports.Services;
|
using DevExpress.XtraReports.Services;
|
||||||
using DevExpress.Blazor.Reporting;
|
using DevExpress.Blazor.Reporting;
|
||||||
using DevExpress.XtraReports.Web.Extensions;
|
using DevExpress.XtraReports.Web.Extensions;
|
||||||
@@ -11,8 +12,12 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
|||||||
builder.RootComponents.Add<App>("#app");
|
builder.RootComponents.Add<App>("#app");
|
||||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
|
builder.Services.Configure<ApiOptions>(opts =>
|
||||||
|
builder.Configuration.GetSection(ApiOptions.SectionName).Bind(opts));
|
||||||
|
builder.Services.AddScoped<EnvelopeGenerator.ReceiverUI.Services.DocumentService>();
|
||||||
|
|
||||||
builder.Services.AddDevExpressWebAssemblyBlazorReportViewer();
|
builder.Services.AddDevExpressWebAssemblyBlazorReportViewer();
|
||||||
|
builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer();
|
||||||
|
|
||||||
builder.Services.AddDevExpressBlazorReportingWebAssembly(configure => {
|
builder.Services.AddDevExpressBlazorReportingWebAssembly(configure => {
|
||||||
configure.UseDevelopmentMode();
|
configure.UseDevelopmentMode();
|
||||||
|
|||||||
27
EnvelopeGenerator.ReceiverUI/Services/DocumentService.cs
Normal file
27
EnvelopeGenerator.ReceiverUI/Services/DocumentService.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using EnvelopeGenerator.ReceiverUI.Options;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.ReceiverUI.Services;
|
||||||
|
|
||||||
|
public class DocumentService(HttpClient http, IOptions<ApiOptions> apiOptions)
|
||||||
|
{
|
||||||
|
private readonly ApiOptions _api = apiOptions.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the PDF bytes for the given envelope key from the API.
|
||||||
|
/// Returns null bytes with the HTTP status code on failure.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<(byte[]? Bytes, HttpStatusCode StatusCode)> GetDocumentAsync(string envelopeKey, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var response = await http.GetAsync($"{_api.BaseUrl}/api/Document/{Uri.EscapeDataString(envelopeKey)}", cancel);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return (null, response.StatusCode);
|
||||||
|
|
||||||
|
var bytes = await response.Content.ReadAsByteArrayAsync(cancel);
|
||||||
|
return (bytes, response.StatusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -6,4 +6,5 @@
|
|||||||
@using EnvelopeGenerator.ReceiverUI
|
@using EnvelopeGenerator.ReceiverUI
|
||||||
@using EnvelopeGenerator.ReceiverUI.Shared
|
@using EnvelopeGenerator.ReceiverUI.Shared
|
||||||
@using DevExpress.Blazor.Reporting
|
@using DevExpress.Blazor.Reporting
|
||||||
|
@using DevExpress.Blazor.PdfViewer
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
5
EnvelopeGenerator.ReceiverUI/wwwroot/appsettings.json
Normal file
5
EnvelopeGenerator.ReceiverUI/wwwroot/appsettings.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Api": {
|
||||||
|
"BaseUrl": "https://localhost:8088"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf
Normal file
BIN
EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf
Normal file
Binary file not shown.
@@ -193,13 +193,12 @@ public static class Extensions
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Envelope
|
#region Envelope
|
||||||
public static CreateEnvelopeCommand CreateEnvelopeCommand(this Faker fake, int userId) => new()
|
public static CreateEnvelopeCommand CreateEnvelopeCommand(this Faker fake, int userId) => new CreateEnvelopeCommand()
|
||||||
{
|
{
|
||||||
Message = fake.Lorem.Paragraph(fake.Random.Number(2, 5)),
|
Message = fake.Lorem.Paragraph(fake.Random.Number(2, 5)),
|
||||||
Title = fake.Lorem.Words(fake.Random.Number(3, 4)).Join(" "),
|
Title = fake.Lorem.Words(fake.Random.Number(3, 4)).Join(" "),
|
||||||
UserId = userId,
|
|
||||||
UseSQLExecutor = false
|
UseSQLExecutor = false
|
||||||
};
|
}.WithAuth(userId);
|
||||||
|
|
||||||
public static List<CreateEnvelopeCommand> CreateEnvelopeCommands(this Faker fake, params int[] userIDs)
|
public static List<CreateEnvelopeCommand> CreateEnvelopeCommands(this Faker fake, params int[] userIDs)
|
||||||
=> Enumerable.Range(0, userIDs.Length)
|
=> Enumerable.Range(0, userIDs.Length)
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using EnvelopeGenerator.Domain.Constants;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Tests.Domain;
|
|
||||||
|
|
||||||
public class ConstantsTests
|
|
||||||
{
|
|
||||||
[TestCase(EnvelopeSigningType.ReadAndSign, EnvelopeSigningType.ReadAndSign)]
|
|
||||||
[TestCase(EnvelopeSigningType.WetSignature, EnvelopeSigningType.WetSignature)]
|
|
||||||
[TestCase((EnvelopeSigningType)5, EnvelopeSigningType.WetSignature)]
|
|
||||||
public void Normalize_ReturnsExpectedValue(EnvelopeSigningType input, EnvelopeSigningType expected)
|
|
||||||
{
|
|
||||||
var normalized = input.Normalize();
|
|
||||||
|
|
||||||
Assert.That(normalized, Is.EqualTo(expected));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,10 +35,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Tests", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{FB2D306B-1042-4A70-31ED-F991A1599371}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{FB2D306B-1042-4A70-31ED-F991A1599371}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -89,14 +89,14 @@ Global
|
|||||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.Build.0 = Release|Any CPU
|
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -116,8 +116,8 @@ Global
|
|||||||
{211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
{211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||||
{224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146}
|
{224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146}
|
||||||
{EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
{EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||||
{FB2D306B-1042-4A70-31ED-F991A1599371} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
|
||||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||||
|
{FB2D306B-1042-4A70-31ED-F991A1599371} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}
|
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}
|
||||||
|
|||||||
Reference in New Issue
Block a user