Custom functions
Extend Art2link ESB with reusable C# functions, callable from maps, port and adapter properties, pipeline component configuration, and routing expressions.
A Custom Function is a C# method that becomes callable from anywhere inside its Application that accepts a {{Function.fnX}} binding token, inside maps, on port and adapter properties, in pipeline component configuration, and in routing and subscription expressions. Inside Maps, custom functions can also be called natively from XSLT via art:invoke.
Typical use cases include database lookups, external API calls, custom string manipulation, cryptographic helpers, and any business rule that is easier to express in C# than in XSLT or XPath. The built-in XSLT 3.0 / XPath 3.1 function library is comprehensive, reach for a custom function when it isn't.
Custom Functions are written and edited in the Art2link ESB UI, compiled and deployed automatically when you save, no build steps, no restarts. Once deployed, a function can be referenced from any map, port, pipeline, or expression in the Application that owns it.
The list view follows the platform's Selected Application behavior, with an Application selected, the list filters to it and the Application column is hidden; with no selection, the list spans every Application you have access to and the column is shown. Creating a new function uses the same flow: with an Application selected, the editor opens with that Application preemptively chosen; without one, the form exposes an Application picker. Custom functions are managed by the Application Owner and the Application Contributor, the same permission that governs every other artifact in the Application, no system-level role is involved.
1. Create a new Custom Function, start from the template provided in the Art2link ESB UI.
2. Write your function, add a public static method to the static class and decorate it with the [CustomFunction("fnName")] attribute.
3. Save, Art2link ESB compiles and deploys the function automatically.
4. Reference it, invoke the function from any binding-aware location: an XSLT map, a port or adapter property, a pipeline component setting, or a routing expression.
| Rule | Details |
|---|---|
| Static class | The class must be declared static. |
| Namespace | Custom.Functions is required. The class name is yours to choose and serves as a logical grouping for your functions. |
| One class per function | Each custom function saved in the UI contains exactly one class; the class may expose multiple [CustomFunction] methods. |
| Methods | Each function must be public static. Async methods are not supported. |
| Rule | Details |
|---|---|
| Namespace | CC.Art2link.Attributes |
| Parameter | The function name used to invoke the method from any binding-aware location. Must follow standard C# method naming rules (no spaces, no special characters). |
| Uniqueness | Must be unique within the Application that owns the function. Another Application can define a function with the same name, it is a separate artifact with its own code. |
| Convention | The fn prefix (e.g., fnLookupCustomer) is recommended but not required. |
Both parameters and return types support: string, int, bool, double, decimal, and collections. Multiple parameters are supported.
using CC.Art2link.Attributes; namespace Custom.Functions; public static class MyFunctions { [CustomFunction("fnMyFunction")] public static string MyFunction(string input) { // TODO: Implement your logic here. return input; } }
using CC.Art2link.Attributes; namespace Custom.Functions; public static class StringFunctions { [CustomFunction("fnToTitleCase")] public static string ToTitleCase(string input) { if (string.IsNullOrWhiteSpace(input)) return input; return System.Globalization.CultureInfo.CurrentCulture .TextInfo.ToTitleCase(input.ToLower()); } [CustomFunction("fnParseBool")] public static bool ParseBool(string input) { return input?.Trim().ToLower() is "1" or "true" or "yes" or "y"; } }
Unhandled exceptions thrown by a custom function will surface as errors at the call site, a map execution error, a port-property resolution error, a pipeline component error, or a routing-expression failure, depending on where the function was invoked. Implement exception handling within your functions and either return a meaningful fallback value or rethrow with additional context.
[CustomFunction("fnSafeParseDate")] public static string SafeParseDate(string input, string format) { try { var date = DateTime.ParseExact(input, format, CultureInfo.InvariantCulture); return date.ToString("yyyy-MM-dd"); } catch (FormatException ex) { throw new InvalidOperationException( $"fnSafeParseDate: could not parse '{input}' with format '{format}'", ex); } }
Once a custom function is saved and compiled, it can be called from anywhere in its Application that resolves binding tokens, and from inside an XSLT map natively. Every call site references the function by its [CustomFunction] attribute name, no class or namespace needed. Call sites in other Applications cannot see it.
| Call site | How to call |
|---|---|
| Maps | Binding token {{Function.fnX(…)}} or native XSLT call art:invoke('fnX', …). |
| Port & adapter properties | Binding token in any property value field on a receive, send, loopback, or null port, including adapter-specific settings. |
| Pipeline component configuration | Binding token in any component property value, resolved at runtime before the component executes. |
| Routing & subscription expressions | Used as the predicate or value in a message type subscription expression. Returns are coerced as needed. |
The two invocation styles below apply specifically inside maps; the other call sites accept the binding-token form only.
The same double-curly binding used for variables, promoted properties, and other map references. After typing {{, intellisense exposes the available custom functions alongside the other binding sources. Calls written this way are resolved by a preprocessor pass before the XSLT runs, so the function executes outside the XSLT context and no XPath context is shared with it.
{{Function.fnFunctionName(parameter)}}
For example, to call fnParseBool:
{{Function.fnParseBool(parameter)}}
Custom functions can also be called inline from XSLT as native functions. This requires declaring the urn:art2link:functions namespace on the stylesheet and excluding its prefix from the result tree:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:art="urn:art2link:functions"
exclude-result-prefixes="xs art">
With the namespace declared, invoke any custom function via art:invoke, passing the function name as the first argument and the function's parameters after it:
art:invoke('fnFormatDateTime', string(CreatedAt))
Parameters follow standard XPath nomenclature, so any XPath expression, field references, casts, computed values, can be passed in. The function executes and its return value is used inline in the map.
A common use case for custom functions is performing database lookups during map execution, for example, enriching a message with customer data from a SQL database.
One deliberate, bounded call like this is fine; capture map calls once in an xsl:variable, and leave bulk per-record enrichment to a port. See Keep custom functions lean.
using CC.Art2link.Attributes; using System.Data; using Microsoft.Data.SqlClient; namespace Custom.Functions; public static class DatabaseLookup { [CustomFunction("fnLookupCustomerName")] public static string LookupCustomerName( string customerId, string connectionString) { try { using var conn = new SqlConnection(connectionString); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT Name FROM Customers WHERE Id = @Id"; cmd.Parameters.AddWithValue("@Id", customerId); var result = cmd.ExecuteScalar(); return result?.ToString() ?? "Unknown"; } catch (Exception ex) { throw new InvalidOperationException( $"fnLookupCustomerName failed for ID '{customerId}': {ex.Message}", ex); } } }
| Aspect | Details |
|---|---|
| Target framework | .NET 8 |
| Mapping engine | SaxonHE 12.9 (XSLT 3.0, XPath 3.1, XQuery 3.1) |
| Available libraries | Built-in .NET libraries. Additional NuGet packages are registered in the Application's own catalog via the Art2link ESB UI (name and version), then referenced with using statements at the top of your function code. See the NuGet packages article. |
| Async | Not supported. All custom function methods must be synchronous. |
| External resources | HTTP calls, database queries, and file system access are permitted. |
| Logging | No logging interface is available at this time. |
| Hot-reload | Saving a function compiles and deploys it immediately. Same in-flight behavior caveats as pipeline components apply. |
Wrap function logic in try/catch. Unhandled exceptions surface as errors at the call site, map, port, pipeline, expression, and are harder to diagnose than a clear error message from your code.
The [CustomFunction] name is immutable once any caller depends on it. Use descriptive, stable identifiers.
Pass connection strings, API keys, and environment-specific values as parameters rather than hardcoding them. This makes functions reusable across environments.
SaxonHE 12.9 includes a comprehensive function library. Use custom functions only when built-in XSLT/XPath functions cannot meet the requirement.
Use the class name as a logical namespace. Group related functions together (e.g., StringFunctions, DatabaseLookup, DateHelpers).
Same as pipeline components, save changes when no messages are actively being processed to avoid unpredictable in-flight behavior.
Ready to transform
Write your logic in the function editor, save, and reference it from your XSLT maps, port and adapter properties, pipeline components, or routing expressions. Art2link ESB handles compilation and deployment automatically.