using System.Collections; using System.Reflection; using FastReport; using FastReport.Export.Pdf; using Newtonsoft.Json; using PdfSharpCore.Pdf; using PdfSharpCore.Pdf.IO; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); var templateRoot = Path.Combine( AppContext.BaseDirectory, builder.Configuration["TemplateRoot"] ?? "wwwroot"); app.Logger.LogInformation("Template root: {TemplateRoot}", templateRoot); // Index every public type in this assembly by its short name so the JSON // payload can refer to a model class by name (e.g. "abnormal_meter_not_send_report"). var typeIndex = Assembly.GetExecutingAssembly().GetTypes() .Where(t => t.IsClass && t.IsPublic) .ToDictionary(t => t.Name, t => t, StringComparer.Ordinal); app.MapGet("/health", () => Results.Ok(new { status = "ok", time = DateTime.UtcNow })); // Render a single template + dataset to a PDF. // Body shape: // { // "template": "report_6", // "dataSourceName": "abnormal_meter_not_send_report", // "typeName": "abnormal_meter_not_send_report", // class in shared report models // "rows": [ { ...same JSON as the C# object would serialize... } ], // "fileName": "report.pdf" // } app.MapPost("/render", async (HttpRequest req) => { using var sr = new StreamReader(req.Body); var bodyText = await sr.ReadToEndAsync(); var body = JsonConvert.DeserializeObject(bodyText) ?? throw new ArgumentException("Empty body"); var pdf = RenderSingle(templateRoot, body, typeIndex); return Results.File(pdf, "application/pdf", body.FileName ?? $"{body.Template}.pdf"); }); // Render multiple sections and merge them into a single PDF. app.MapPost("/render/multi", async (HttpRequest req) => { using var sr = new StreamReader(req.Body); var bodyText = await sr.ReadToEndAsync(); var body = JsonConvert.DeserializeObject(bodyText) ?? throw new ArgumentException("Empty body"); if (body.Sections == null || body.Sections.Count == 0) return Results.BadRequest(new { error = "sections must contain at least one entry" }); var streams = body.Sections.Select(s => new MemoryStream(RenderSingle(templateRoot, s, typeIndex))).ToList(); var merged = MergePdfs(streams); foreach (var s in streams) s.Dispose(); return Results.File(merged, "application/pdf", body.FileName ?? "merged.pdf"); }); app.Run(); return; // ---- helpers -------------------------------------------------------------- static byte[] RenderSingle(string templateRoot, RenderRequest req, Dictionary typeIndex) { if (string.IsNullOrWhiteSpace(req.Template)) throw new ArgumentException("template is required"); var path = ResolveTemplatePath(templateRoot, req.Template); if (!File.Exists(path)) throw new FileNotFoundException($"Template not found: {req.Template}", path); using var report = new Report(); report.Load(path); if (req.Rows != null && !string.IsNullOrEmpty(req.TypeName)) { if (!typeIndex.TryGetValue(req.TypeName, out var elementType)) throw new ArgumentException($"Unknown typeName: {req.TypeName}. Add the class to pwa_api/Modules/Report/Models/ so the sidecar can compile it in."); var listType = typeof(List<>).MakeGenericType(elementType); var typedList = (IList?)JsonConvert.DeserializeObject(req.Rows.ToString(), listType); if (typedList == null) throw new ArgumentException("Failed to deserialize rows."); var dsName = req.DataSourceName ?? elementType.Name; report.RegisterData((IEnumerable)typedList, dsName); var ds = report.GetDataSource(dsName); if (ds != null) ds.Enabled = true; } report.Prepare(); using var ms = new MemoryStream(); var pdfExport = new PDFExport(); report.Export(pdfExport, ms); return ms.ToArray(); } static string ResolveTemplatePath(string root, string name) { if (name.EndsWith(".frx", StringComparison.OrdinalIgnoreCase)) { var direct = Path.Combine(root, name); if (File.Exists(direct)) return direct; } var withExt = name.EndsWith(".frx") ? name : name + ".frx"; var inReports = Path.Combine(root, "reports", withExt); if (File.Exists(inReports)) return inReports; var inDocuments = Path.Combine(root, "documents", withExt); if (File.Exists(inDocuments)) return inDocuments; return inReports; } static byte[] MergePdfs(IEnumerable sources) { using var output = new PdfDocument(); foreach (var src in sources) { src.Position = 0; using var input = PdfReader.Open(src, PdfDocumentOpenMode.Import); for (var i = 0; i < input.PageCount; i++) output.AddPage(input.Pages[i]); } using var ms = new MemoryStream(); output.Save(ms, false); return ms.ToArray(); } // ---- DTOs ----------------------------------------------------------------- public class RenderRequest { public string Template { get; set; } = ""; public string? DataSourceName { get; set; } public string? TypeName { get; set; } public Newtonsoft.Json.Linq.JArray? Rows { get; set; } public string? FileName { get; set; } } public class MultiRenderRequest { public string? FileName { get; set; } public List? Sections { get; set; } }