Skip to content
Art2link ESB v2.02 LTS HomeDocumentationBlogContact
Build & extend/Custom functions
Developer Reference

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 the Art2link ESB platform 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, they can be referenced from any map, port, pipeline, or expression in the system.

AUTHOR ONCE — CALL FROM ANYWHERE MyFunctions C# [CustomFunction("fnLookup")] public static string Lookup(string id) { // … } Save in the UI — no build step. COMPILE SYSTEM-WIDE Function library fnLookup fnParseBool fnFormatDate CALL Maps {{Function.fn…}} / art:invoke Port & adapter properties {{Function.fn…}} Pipeline component config {{Function.fn…}} Routing & subscriptions predicate / expression One library, every call site. Edit the C# file and every caller picks up the new code on its next invocation.
Custom functions are system-scoped, not application-scoped. A custom function lives at the platform level — one function library is visible to every Application. Any Map, port, pipeline component, or expression in any Application can call any defined function by its [CustomFunction] name, and editing a function's code propagates to every caller across the whole system. This is the opposite of maps themselves, which are owned by a single Application.
PLATFORM SCOPE SYSTEM-WIDE Custom function library one library · visible to every Application Application A maps · ports · pipelines calls fnLookup, fnParseBool Application B maps · ports · pipelines calls fnLookup, fnFormatDate Application C maps · ports · pipelines calls fnFormatDate every other Application same library

QUICK START — FOUR STEPS 1 Start from template New function in the Custom Functions UI 2 Write the function [CustomFunction( "fnLookup")] public static string Lookup(string id) { … } Public static method 3 Save Auto-compiled and deployed NO BUILD · NO RESTART 4 Call it {{Function. fnLookup(id) }} From a map, port, pipeline, or expression Edit → save → the next caller sees the new code. The whole loop is the UI; no deploy step.

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.


Class Rules
RuleDetails
Static classThe class must be declared static.
NamespaceCustom.Functions is required. The class name is yours to choose and serves as a logical grouping for your functions.
One class per fileEach file must contain exactly one class.
MethodsEach function must be public static. Async methods are not supported.
[CustomFunction] Attribute
RuleDetails
NamespaceCC.Art2link.Attributes
ParameterThe function name used to invoke the method from any binding-aware location. Must follow standard C# method naming rules (no spaces, no special characters).
UniquenessMust be unique system-wide across all custom functions in the Art2link ESB instance.
ConventionThe fn prefix (e.g., fnLookupCustomer) is recommended but not required.
Supported Types

Both parameters and return types support: string, int, bool, double, decimal, and collections. Multiple parameters are supported.

⚠️
Changing the function name after deployment will break any caller that references it. Treat it as an immutable identifier once in use.
Basic Template
C#CustomFunctionTemplate.cs
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;
    }
}
Multiple Functions Example
C#StringFunctions.cs
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.

C#Error handling pattern
[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 the platform 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.

Where functions can be called from
Call siteHow to call
MapsBinding token {{Function.fnX(…)}} or native XSLT call art:invoke('fnX', …).
Port & adapter propertiesBinding token in any property value field on a receive, send, loopback, or null port — including adapter-specific settings.
Pipeline component configurationBinding token in any component property value, resolved at runtime before the component executes.
Routing & subscription expressionsUsed as the predicate or value in a message type subscription expression. Returns are coerced as needed.
Pipeline component code cannot call custom functions. A pipeline component’s C# body has no way to import or reference a custom function directly. Only the component’s property values can — the platform resolves any {{Function.fnX}} token to its return value before the component executes, so the component receives the final string, never the function itself.

The two invocation styles below apply specifically inside maps; the other call sites accept the binding-token form only.

Binding syntax (preprocessor)

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.

SyntaxBinding expression
{{Function.fnFunctionName(parameter)}}

For example, to call fnParseBool:

ExampleMap binding
{{Function.fnParseBool(parameter)}}
XSLT function call (art:invoke)

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:

XSLTStylesheet declaration
<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:

XSLTMap expression
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.

The connection string is passed as a parameter to the function, allowing you to configure it in the Art2link ESB UI without hardcoding it in your code.

C#DatabaseLookup.cs
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);
        }
    }
}
📦
NuGet package required. The Microsoft.Data.SqlClient package must first be added through the Art2link ESB UI (name and version). Once added, reference it with a using statement at the top of your function code. See the NuGet packages article for the global catalogue and impact analysis.
Tip: Pass the connection string as a parameter rather than hardcoding it. This lets you use different databases per environment by configuring the value in the Art2link ESB UI.

AspectDetails
Target framework.NET 8
Mapping engineSaxonHE 12.9 (XSLT 3.0, XPath 3.1, XQuery 3.1)
Available librariesBuilt-in .NET libraries. Additional NuGet packages can be added 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 for the global catalogue.
AsyncNot supported. All custom function methods must be synchronous.
External resourcesHTTP calls, database queries, and file system access are permitted.
LoggingNo logging interface is available at this time.
One class per fileEach file must contain exactly one class. A class may contain multiple functions.
Hot-reloadSaving a function compiles and deploys it immediately. Same in-flight behavior caveats as pipeline components apply.
Save-to-live deployment
HOT RELOAD — SAVE-TO-LIVE PATH STEP 1 — YOU Save the function edit in the Art2link UI STEP 2 — AUTO Art2link compiles no build step, no restart SECONDS STEP 3 — PROPAGATE Next call uses new code maps · ports · pipelines routing · subscriptions every caller picks up the new version Edit → save → next message uses the new code. Same model as pipeline components.
⚠️
In-flight message behavior. Saving overwrites the previous version immediately. If a message is mid-flight when you save — for example, partway through a map that calls the function, or queued behind a pipeline component that does — the outcome depends on which step has been reached at the moment of the save. The result is unpredictable on a case-by-case basis. Deploy function changes during periods of no message activity to avoid inconsistent behavior. Same model as pipeline components.

Always handle exceptions

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.

Choose stable function names

The [CustomFunction] name is immutable once any caller depends on it. Use descriptive, stable identifiers.

Parameterize configuration

Pass connection strings, API keys, and environment-specific values as parameters rather than hardcoding them. This makes functions reusable across environments.

Prefer built-in functions first

SaxonHE 12.9 includes a comprehensive function library. Use custom functions only when built-in XSLT/XPath functions cannot meet the requirement.

Group related functions

Use the class name as a logical namespace. Group related functions together (e.g., StringFunctions, DatabaseLookup, DateHelpers).

Deploy during quiet periods

Same as pipeline components — save changes when no messages are actively being processed to avoid unpredictable in-flight behavior.

Ready to transform

Download the custom function template, write your logic, and reference it from your XSLT maps, port and adapter properties, pipeline components, or routing expressions. Art2link ESB handles compilation and deployment automatically.