GroupDocs.Assembly for .NET 26.7 Release Notes
GroupDocs.Assembly for .NET 26.7 refreshes the underlying document generation engines and fixes two template-expression resolution issues — one affecting data models built from interfaces, and one affecting custom KnownTypes functions that take a single DataRow.
Full list of changes in this release
| Key | Category | Summary |
|---|---|---|
| ASSEMBLYNET-137 | ✨ Enhancement | Upgrade document generation engines |
| ASSEMBLYNET-92 | 🐛 Bug Fix | Template expressions could not use members inherited from a base interface |
| ASSEMBLYNET-59 | 🐛 Bug Fix | Custom KnownTypes function taking a single DataRow was not resolved from a JSON object node |
Enhancements
Upgrade document generation engines
The bundled document generation engines have been updated to their latest releases to deliver the best performance, stability, and security across all supported templates and output formats. Template processing, expression evaluation, data-source binding and output rendering across every supported format are now produced by these updated engines.
| Engine | Template/output formats |
|---|---|
| Word-processing engine | DOC, DOCX, DOT, DOTX, RTF, ODT, TXT, HTML, MHTML, XPS, PDF |
| Spreadsheet engine | XLS, XLSX, XLSM, XLSB, XLTX, XLTM, XLAM, ODS, CSV, TSV, MHTML, HTML, XPS, PDF |
| Presentation engine | PPT, PPTX, PPS, PPSX, ODP, XPS, PDF |
| Email engine | EML, EMLX, MSG, MHTML |
| Barcode engine | embedded barcode rendering inside Word, Spreadsheet, Presentation and Email templates |
Note: Because the underlying rendering and serialization engines changed, regenerated documents may differ slightly at the byte/pixel level (anti-aliasing, content-stream packing, text layout, serialization details) while remaining visually and functionally equivalent. If your pipeline compares generated output byte-for-byte against stored baselines, refresh those baselines after upgrading.
Verification. Existing assembly code continues to compile and run; template syntax, expression evaluation, data-source binding and the public API are all source-compatible with the prior release.
using GroupDocs.Assembly;
DocumentAssembler assembler = new DocumentAssembler();
assembler.AssembleDocument(
"template.docx",
"output.docx",
new DataSourceInfo(customer, "customer"));
Fixes
Template expressions could not use members inherited from a base interface
ASSEMBLYNET-92. When the data was exposed through interfaces, a template expression that read a member declared on a base interface failed. The most common case was the logical-NOT operator on a Boolean flag, e.g. <<if [!item.Disabled]>> or items.Where(x => !x.Disabled), where Disabled was declared on a base interface of the element type.
The engine built its member bindings from the element’s interface type only, and an interface does not report the members it inherits from its base interfaces. The inherited member was therefore invisible: it fell back to an untyped binding, so applying ! to it no longer produced a Boolean.
Symptom. System.InvalidOperationException — either “Tag ‘if’ is not well-formed. A conditional expression should return a Boolean value.” or “Can not get the value of member ‘…’ on type ‘…’.”
public interface IToggleable { bool Disabled { get; } } // base interface
public interface IFeatureItem : IToggleable { string Title { get; } } // element type
// data exposed as ICollection<IFeatureItem>
<<foreach [i in items.Where(x => !x.Disabled)]>><<[i.Title]>><</foreach>>
x.Disabled (declared on the base IToggleable) now resolves and evaluates as a Boolean, so the expression works.
Scope.
- Affects data objects exposed through interface types when the referenced member is declared on a base interface.
- Class-based (POCO) data models were never affected — class member lookup already includes the base chain.
- The previously required workaround
x.Disabled == falseis no longer needed; both forms now work. - No effect on templates that already resolved — the change only makes previously-invisible inherited members available.
Custom KnownTypes function taking a single DataRow was not resolved from a JSON object node
ASSEMBLYNET-59. A custom static function registered through DocumentAssembler.KnownTypes that declares a single System.Data.DataRow parameter was not resolved when it was called with a JSON object node such as root.
For a JSON data source, a reference to an object node is exposed to the engine as an enumeration of DataRow, which did not convert to a single DataRow during overload resolution — so only a function declaring an IEnumerable parameter matched. This was inconsistent with ordinary member access on the same reference (root.field), which already uses single-row semantics.
Symptom. System.InvalidOperationException: … Can not resolve method '…' on type '…'.
public static class ContactFunctions
{
public static string FormatName(DataRow contact) =>
$"{contact["title"]} {contact["firstName"]} {contact["lastName"]}";
}
var assembler = new DocumentAssembler();
assembler.KnownTypes.Add(typeof(ContactFunctions));
// template: <<[ContactFunctions.FormatName(root)]>> // now resolves and is invoked
Scope.
- Applies to functions declaring a single
DataRowparameter, called with a JSON object node. - The single-row path is tried only when normal overload resolution finds no match, so calls that already resolved (e.g. an
IEnumerable/Iterable<DataRow>overload) keep their existing binding. - The previous workaround — declaring the parameter as
IEnumerable/objectand taking the firstDataRowinside the method — is no longer required.
Public API and Backwards Incompatible Changes
Important Notes for Generated Documents
Because the underlying document engines were refreshed, regenerated documents may differ at the byte/pixel level (serialisation details, anti-aliasing, content-stream packing) while remaining visually and functionally equivalent. Consumers who byte-compare generated output against stored baselines should refresh those baselines after upgrading; consumers who validate by opening documents or asserting on parsed fields/cell values are unaffected.