feat: implementiert PDF-Generierung mit Hintergrundbild und Dokumentation
This commit is contained in:
144
Server/Controllers/HallOfFamePDFController.cs
Normal file
144
Server/Controllers/HallOfFamePDFController.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Controllers;
|
||||
using SZUAbsolventenverein.Module.HallOfFame.Services;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using QuestPDF.Fluent;
|
||||
using QuestPDF.Helpers;
|
||||
using QuestPDF.Infrastructure;
|
||||
|
||||
namespace SZUAbsolventenverein.Module.HallOfFame.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class HallOfFamePdfController : ModuleControllerBase
|
||||
{
|
||||
private readonly IHallOfFameService _hallOfFameService;
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
public HallOfFamePdfController(IHallOfFameService hallOfFameService, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor)
|
||||
{
|
||||
_hallOfFameService = hallOfFameService;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
// GET: api/<controller>?moduleid=x&download=true/false
|
||||
[HttpGet]
|
||||
[Authorize(Policy = PolicyNames.ViewModule)]
|
||||
public async Task<IActionResult> Get(string moduleid, bool download = false)
|
||||
{
|
||||
int ModuleId;
|
||||
if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId))
|
||||
{
|
||||
var entries = await _hallOfFameService.GetHallOfFamesAsync(ModuleId);
|
||||
var publishedEntries = entries.Where(e => e.Status == "Published").ToList();
|
||||
|
||||
var document = Document.Create(container =>
|
||||
{
|
||||
foreach (var entry in publishedEntries)
|
||||
{
|
||||
// Bild laden falls vorhanden
|
||||
byte[] imageBytes = null;
|
||||
if (!string.IsNullOrEmpty(entry.Image))
|
||||
{
|
||||
try
|
||||
{
|
||||
var fullImagePath = System.IO.Path.Combine(
|
||||
_environment.WebRootPath, entry.Image.TrimStart('/'));
|
||||
if (System.IO.File.Exists(fullImagePath))
|
||||
imageBytes = System.IO.File.ReadAllBytes(fullImagePath);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
container.Page(page =>
|
||||
{
|
||||
page.Size(PageSizes.A4);
|
||||
page.Margin(0);
|
||||
|
||||
page.Content().Layers(layers =>
|
||||
{
|
||||
// Hintergrund-Layer: Bild als Vollbild
|
||||
if (imageBytes != null)
|
||||
{
|
||||
layers.Layer().Image(imageBytes).FitUnproportionally();
|
||||
}
|
||||
else
|
||||
{
|
||||
layers.Layer().Background(Colors.Grey.Darken3);
|
||||
}
|
||||
|
||||
// Inhalt-Layer (PrimaryLayer bestimmt die Größe)
|
||||
layers.PrimaryLayer()
|
||||
.Column(column =>
|
||||
{
|
||||
// === OBEN: Name und Jahr in halbtransparenter grauer Box ===
|
||||
column.Item()
|
||||
.Background("#AA9E9E9E") // halbtransparentes Grau
|
||||
.Padding(20)
|
||||
.Row(row =>
|
||||
{
|
||||
row.RelativeItem().Column(inner =>
|
||||
{
|
||||
inner.Item().Text(entry.Name)
|
||||
.FontSize(28).Bold().FontColor(Colors.White);
|
||||
inner.Item().Text($"Jahrgang {entry.Year}")
|
||||
.FontSize(14).FontColor(Colors.White);
|
||||
});
|
||||
});
|
||||
|
||||
// === UNTEN: Beschreibung am Seitenende ===
|
||||
var description = entry.Description ?? "";
|
||||
var sections = description.Split('\n')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrEmpty(s))
|
||||
.ToArray();
|
||||
|
||||
column.Item().ExtendVertical().AlignBottom()
|
||||
.Background("#CC333333") // dunkles halbtransparentes Overlay
|
||||
.Padding(25)
|
||||
.Column(descColumn =>
|
||||
{
|
||||
if (sections.Length > 0)
|
||||
{
|
||||
descColumn.Item().PaddingBottom(10)
|
||||
.Text("Beschreibung:")
|
||||
.FontSize(16).Bold().FontColor(Colors.White);
|
||||
|
||||
foreach (var line in sections)
|
||||
{
|
||||
descColumn.Item().PaddingBottom(6)
|
||||
.Text(line)
|
||||
.FontSize(11).FontColor(Colors.White)
|
||||
.LineHeight(1.4f);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
byte[] pdfBytes = document.GeneratePdf();
|
||||
|
||||
if (download)
|
||||
{
|
||||
return File(pdfBytes, "application/pdf", "HallOfFame.pdf");
|
||||
}
|
||||
// Inline: PDF wird im Browser angezeigt (Vorschau)
|
||||
return File(pdfBytes, "application/pdf");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame PDF Get Attempt {ModuleId}", moduleid);
|
||||
return Forbid();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.8" />
|
||||
<PackageReference Include="QuestPDF" Version="2026.2.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -34,4 +35,13 @@
|
||||
<Reference Include="Oqtane.Server"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net9.0\Oqtane.Server.dll</HintPath></Reference>
|
||||
<Reference Include="Oqtane.Shared"><HintPath>..\..\oqtane.framework\Oqtane.Server\bin\Debug\net9.0\Oqtane.Shared.dll</HintPath></Reference>
|
||||
</ItemGroup>
|
||||
<!-- Copy QuestPDF DLLs to Oqtane.Server so they are available at runtime -->
|
||||
<Target Name="CopyQuestPdfToOqtane" AfterTargets="Build">
|
||||
<Copy SourceFiles="$(OutputPath)QuestPDF.dll" DestinationFolder="$(ProjectDir)..\..\oqtane.framework\Oqtane.Server\bin\$(Configuration)\$(TargetFramework)\" SkipUnchangedFiles="true" />
|
||||
<ItemGroup>
|
||||
<QuestPdfNativeFiles Include="$(OutputPath)runtimes\**\*.*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(QuestPdfNativeFiles)" DestinationFiles="@(QuestPdfNativeFiles->'$(ProjectDir)..\..\oqtane.framework\Oqtane.Server\bin\$(Configuration)\$(TargetFramework)\runtimes\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Oqtane.Infrastructure;
|
||||
@@ -21,6 +21,9 @@ namespace SZUAbsolventenverein.Module.HallOfFame.Startup
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// QuestPDF Lizenz konfigurieren
|
||||
QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community;
|
||||
|
||||
services.AddTransient<IHallOfFameService, ServerHallOfFameService>();
|
||||
services.AddDbContextFactory<HallOfFameContext>(opt => { }, ServiceLifetime.Transient);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user