Dear GroupDocs users, we’re pleased to announce GroupDocs.Assembly for .NET 26.7. This release refreshes all bundled document generation engines and fixes two template-expression resolution issues — one affecting data models exposed through interfaces, and one affecting custom KnownTypes functions that take a single DataRow. There are no public API changes and no code changes are required to upgrade.
Engine upgrades
All bundled document generation engines have been updated to their latest releases for the best performance, stability, and security across every supported template and output format.
Fix — template expressions couldn’t read members inherited from a base interface
When your 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. (ASSEMBLYNET-92)
Fix — custom KnownTypes function taking a single DataRow not resolved from a JSON object node
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. (ASSEMBLYNET-59)