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

KeyCategorySummary
ASSEMBLYNET-137EnhancementUpgrade document generation engines
ASSEMBLYNET-92🐛 Bug FixTemplate expressions could not use members inherited from a base interface
ASSEMBLYNET-59🐛 Bug FixCustom 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.

EngineTemplate/output formats
Word-processing engineDOC, DOCX, DOT, DOTX, RTF, ODT, TXT, HTML, MHTML, XPS, PDF
Spreadsheet engineXLS, XLSX, XLSM, XLSB, XLTX, XLTM, XLAM, ODS, CSV, TSV, MHTML, HTML, XPS, PDF
Presentation enginePPT, PPTX, PPS, PPSX, ODP, XPS, PDF
Email engineEML, EMLX, MSG, MHTML
Barcode engineembedded 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 == false is 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 DataRow parameter, 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/object and taking the first DataRow inside 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.