| name | shesha-document-generation |
| description | Generates Shesha document generation artifacts for producing PDFs or Word documents from Word templates via Aspose mail merge. Creates DTOs, item classes, AutoMapper profiles, application services, controllers, Word template guidance, and sample Word document templates with proper merge fields. Use when the user asks to create, scaffold, or implement document generation, mail merge, Word-to-PDF conversion, Word document generation, document processing, or Word document templates in a Shesha project. |
Shesha Document Generation
Generate document generation artifacts for a Shesha/.NET application that uses Shesha.Enterprise.DocumentProcessing to produce PDFs or Word documents from Word templates via Aspose mail merge, based on $ARGUMENTS.
Instructions
- Inspect nearby files to determine the correct namespace root.
- Ask the user which template source approach to use if not specified:
- FileTemplateConfiguration by name (default) — template uploaded via admin UI, resolved by name at runtime
- Embedded resource — template bundled in the assembly as an embedded resource, uses
AsposeBuilderBase.GetTemplate()
- Direct Aspose with dictionary — manual template lookup, field names as string keys, most control
- Save to StoredFile — same as (1) or (2) but also persists the generated PDF to a
StoredFile on the entity
- StoredFile as template source — template loaded from a
StoredFile property on an entity (e.g., a config entity's template field), uses IStoredFileService.GetStreamAsync() to load and Aspose directly for mail merge
- Ask the user which output format to use if not specified:
- PDF (default) — generates a PDF via
document.Save(stream, SaveFormat.Pdf)
- Word (.docx) — returns the populated Word document via
document.Save(stream, SaveFormat.Docx), preserving editability
- Both — generates separate endpoints for PDF and Word download
- Ask the user which exposure approach to use if not specified:
- Application Service (default) — inherits
SheshaAppServiceBase, auto-registered as API endpoint
- Controller — inherits
ControllerBase, manual [Route]/[HttpGet] attributes
- Dictionary-based controller — inherits
ControllerBase, uses Dictionary<string, object> instead of a typed DTO
- When the output format is Word only, use
DocumentDto instead of PdfDto in class names and Documents instead of PdfDocuments in folder names. When PDF or Both, keep PdfDto/PdfDocuments.
- All entity properties on DTOs should be
string (pre-formatted) unless they are byte[] (signatures/images) or DataTable/DataSet (regions).
- DTO property names must match Word template merge field names exactly.
- Use
{Placeholder} tokens throughout generated code so the user can replace them with project-specific values.
- After generating document generation code artifacts, ALWAYS ask the user: "Would you like me to generate a sample Word document template (
.docx) with the correct merge fields for easy testing and subsequent modification?" If yes, generate the template using word-template-generator.md.
- When creating or updating Word templates, use the complex field character approach (
w:fldChar begin/separate/end) — never w:fldSimple. See word-template-generator.md for the full reference.
Word Template Generation Rules
When generating sample .docx templates:
- Merge fields must use
w:fldChar complex field characters (begin → instrText → separate → display → end). The w:fldSimple element does NOT produce real merge fields recognized by Word or Aspose.
- Repeating regions use
TableStart:RegionName and TableEnd:RegionName merge fields. Place them in separate table rows around the data row. The data row (containing field merge fields) is the one that repeats. Aspose removes the marker rows during mail merge.
- Nested regions (parent-child): the child
TableStart/TableEnd must be physically nested inside the parent's start/end markers.
- Individual character boxes (ID numbers, PERSAL numbers): split into individual merge fields with a prefix and zero-based index (e.g.,
P0, P1, ..., P7).
- Field names must be PascalCase and match the DTO property names exactly (case-sensitive).
- The generated template should be placed alongside the generated code artifacts or in the project root for easy access.
- Clean up
node_modules, package-lock.json, and the generator script after template generation.
Artifact Catalog
Folder Structure
{ModuleName}.Application/
PdfDocuments/{DocumentName}/
{DocumentName}AppService.cs -- or {DocumentName}Controller.cs
Dtos/
{DocumentName}PdfDto.cs
{ItemName}Section.cs -- one per repeating region
{DocumentName}MappingProfile.cs -- if using typed DTO + AutoMapper
Quick Reference
Key Types from Shesha.Enterprise.DocumentProcessing
| Type | Purpose |
|---|
DocumentProcessManager | Main service — GenerateAsync<T>(dto, templateName) returns Stream, GetDataTable<T>(list, tableName) returns DataTable |
FileTemplateConfiguration | Maps a template name (string) to a StoredFile in the database |
AsposeBuilderBase | Base class with GetTemplate(), AddPersonSignature(), ReplaceRichTextField(), embedded resource support |
BaseDocumentProcessor | Extended base with field settings parsing, cleanup options, rich text items |
DocumentProcessManager API
Stream stream = await _documentProcessManager.GenerateAsync(pdfDto, "TemplateName");
DataTable table = _documentProcessManager.GetDataTable(items, "RegionName");
StoredFile file = await _documentProcessManager.SaveFileAsync(stream, ownerType, ownerId, "TemplateName");
Aspose Mail Merge Cleanup Options
Important: MailMergeCleanupOptions requires using Aspose.Words.MailMerging; — this is a separate namespace from Aspose.Words.
using Aspose.Words.MailMerging;
builder.Document.MailMerge.CleanupOptions =
MailMergeCleanupOptions.RemoveEmptyParagraphs |
MailMergeCleanupOptions.RemoveUnusedFields |
MailMergeCleanupOptions.RemoveUnusedRegions;
Signature Pattern
public byte[] {Role}Signature { get; set; }
using var sigStream = await _storedFileService.GetStreamAsync(person.SignatureFile);
using var ms = new MemoryStream();
await sigStream.CopyToAsync(ms);
dto.{Role}Signature = ms.ToArray();
DataTable Region (Repeating Rows)
public DataTable {RegionName} { get; set; }
var items = entities.Select(e => new {RegionItem}
{
Name = e.Name,
Value = e.Value?.ToString() ?? ""
}).ToList();
dto.{RegionName} = _documentProcessManager.GetDataTable(items, "{RegionName}");
Nested Region (Parent-Child DataSet)
var parentTable = _documentProcessManager.GetDataTable(parentItems, "ParentRegion");
var childTable = _documentProcessManager.GetDataTable(childItems, "ChildRegion");
var dataSet = new DataSet();
dataSet.Tables.Add(parentTable.Copy());
dataSet.Tables.Add(childTable.Copy());
dataSet.Relations.Add(new DataRelation("ParentChildRelation",
dataSet.Tables["ParentRegion"].Columns["ParentId"],
dataSet.Tables["ChildRegion"].Columns["ParentId"]));
dto.ParentRegion = dataSet.Tables["ParentRegion"];
dto.ChildRegion = dataSet.Tables["ChildRegion"];
dto.ParentChildDataSet = dataSet;
HTML Stripping (for rich text fields stored as HTML)
using System.Text.RegularExpressions;
private static string StripHtml(string html)
{
if (string.IsNullOrEmpty(html)) return "";
return Regex.Replace(html, "<.*?>", string.Empty).Trim();
}
Common Patterns
Return PDF as download:
using var documentStream = await _documentProcessManager.GenerateAsync(pdfDto, templateName);
using var memoryStream = new MemoryStream();
await documentStream.CopyToAsync(memoryStream);
var bytes = memoryStream.ToArray();
return File(bytes, "application/pdf", fileName);
Return Word document as download (no PDF conversion):
var document = new Document(templateStream);
document.MailMerge.Execute(fieldNames, fieldValues);
using var memoryStream = new MemoryStream();
document.Save(memoryStream, SaveFormat.Docx);
var bytes = memoryStream.ToArray();
var fileName = $"{DocumentName}_{DateTime.Now:yyyyMMdd}.docx";
return File(bytes, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", fileName);
See document-service.md SS5 for the full application service and controller templates.
Save PDF to entity StoredFile property:
using var documentStream = await _documentProcessManager.GenerateAsync(pdfDto, templateName);
using var memoryStream = new MemoryStream();
await documentStream.CopyToAsync(memoryStream);
memoryStream.Position = 0;
var storedFile = await _storedFileService.SaveFileAsync(
memoryStream,
fileName,
file => { file.FileType = "application/pdf"; });
entity.PdfDocument = storedFile;
await _repository.UpdateAsync(entity);
Generate safe file name:
private static string GenerateFileName(string prefix, string identifier)
{
var fileName = $"{prefix}_{identifier}_{DateTime.Now:yyyyMMdd}.pdf";
return string.Join("_", fileName.Split(Path.GetInvalidFileNameChars()));
}
Now generate the requested artifact(s) based on: $ARGUMENTS