Skip to content

Configuration Templates and Variable Substitution

Apq.Cfg supports variable references in configuration values, enabling dynamic composition and reuse of configurations.

Comparison with Microsoft Configuration: Microsoft.Extensions.Configuration does not support variable substitution. This is a differentiating feature of Apq.Cfg, particularly useful for avoiding configuration duplication and dynamic path composition.

Basic Usage

Referencing Other Configurations

csharp
// config.json
{
    "App": {
        "Name": "MyApp",
        "LogPath": "${App:Name}/logs",
        "DataPath": "${App:Name}/data"
    }
}
csharp
using Apq.Cfg;

var cfg = new CfgBuilder()
    .AddJsonFile("config.json", level: 0, writeable: false)
    .Build();

// Use GetResolved to get the resolved value
var logPath = cfg.GetResolved("App:LogPath");
// Returns: "MyApp/logs"

var dataPath = cfg.GetResolved("App:DataPath");
// Returns: "MyApp/data"

Referencing Environment Variables

Use ${ENV:VariableName} syntax to reference environment variables:

csharp
// config.json
{
    "Paths": {
        "Home": "${ENV:USERPROFILE}",
        "Temp": "${ENV:TEMP}",
        "AppData": "${ENV:APPDATA}/MyApp"
    }
}
csharp
var homePath = cfg.GetResolved("Paths:Home");
// Returns: "C:\Users\username"

Referencing System Properties

Use ${SYS:PropertyName} syntax to reference system properties:

csharp
// config.json
{
    "System": {
        "Machine": "${SYS:MachineName}",
        "User": "${SYS:UserName}",
        "LogFile": "logs/${SYS:MachineName}_${SYS:Today}.log"
    }
}
csharp
var logFile = cfg.GetResolved("System:LogFile");
// Returns: "logs/SERVER01_2026-01-02.log"

Supported System Properties

PropertyDescription
MachineNameComputer name
UserNameCurrent user name
UserDomainNameUser domain name
OSVersionOperating system version
ProcessIdCurrent process ID
CurrentDirectoryCurrent working directory
SystemDirectorySystem directory
ProcessorCountNumber of processors
Is64BitProcessWhether 64-bit process
Is64BitOperatingSystemWhether 64-bit OS
CLRVersionCLR version
NowCurrent time (ISO 8601 format)
UtcNowCurrent UTC time
TodayCurrent date (yyyy-MM-dd)

Nested References

Variables can be nested, supporting multi-level resolution:

csharp
// config.json
{
    "App": {
        "Name": "MyApp",
        "Version": "1.0.0"
    },
    "Paths": {
        "Base": "${ENV:APPDATA}/${App:Name}",
        "Data": "${Paths:Base}/data/${App:Version}"
    }
}
csharp
var dataPath = cfg.GetResolved("Paths:Data");
// Returns: "C:\Users\username\AppData\Roaming\MyApp/data/1.0.0"

Resolving Template Strings

Besides getting configuration values, you can directly resolve any template string:

csharp
var template = "Application ${App:Name} v${App:Version} running on ${SYS:MachineName}";
var result = cfg.ResolveVariables(template);
// Returns: "Application MyApp v1.0.0 running on SERVER01"

Type Conversion

The GetResolved<T> method supports converting resolved values to specified types:

csharp
// config.json
{
    "Settings": {
        "BasePort": "8080",
        "Port": "${Settings:BasePort}"
    }
}
csharp
var port = cfg.GetResolved<int>("Settings:Port");
// Returns: 8080 (int)

Batch Retrieval

csharp
var keys = new[] { "App:LogPath", "App:DataPath", "Paths:Home" };
var values = cfg.GetManyResolved(keys);

foreach (var (key, value) in values)
{
    Console.WriteLine($"{key} = {value}");
}

Custom Resolution Options

Modify Variable Syntax

csharp
var options = new VariableResolutionOptions
{
    VariablePrefix = "#{",    // Default "${"
    VariableSuffix = "}#",    // Default "}"
    PrefixSeparator = "."     // Default ":"
};
options.Resolvers.Add(VariableResolvers.Config);

// Use custom syntax: #{App.Name}#
var result = cfg.GetResolved("Key", options);

Control Recursion Depth

csharp
var options = new VariableResolutionOptions
{
    MaxRecursionDepth = 5  // Default 10
};

Handling Unresolved Variables

csharp
var options = new VariableResolutionOptions
{
    // Keep: Keep original expression (default)
    // Empty: Replace with empty string
    // Throw: Throw exception
    UnresolvedBehavior = UnresolvedVariableBehavior.Throw
};

Configuration via CfgBuilder

csharp
var cfg = new CfgBuilder()
    .AddJsonFile("config.json", level: 0, writeable: false)
    .ConfigureVariableResolution(options =>
    {
        options.MaxRecursionDepth = 5;
        options.UnresolvedBehavior = UnresolvedVariableBehavior.Empty;
    })
    .Build();

Adding Custom Resolvers

csharp
// Custom resolver
public class CustomResolver : IVariableResolver
{
    public string? Prefix => "CUSTOM";

    public string? Resolve(string variableName, ICfgRoot cfg)
    {
        return variableName switch
        {
            "Timestamp" => DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),
            "Guid" => Guid.NewGuid().ToString(),
            _ => null
        };
    }
}

// Register custom resolver
var cfg = new CfgBuilder()
    .AddJsonFile("config.json", level: 0, writeable: false)
    .AddVariableResolver(new CustomResolver())
    .Build();

// Usage: ${CUSTOM:Timestamp}

Circular Reference Detection

The template engine automatically detects circular references and throws an exception:

csharp
// config.json - circular reference
{
    "A": "${B}",
    "B": "${A}"
}
csharp
// Throws InvalidOperationException: Circular reference detected: A
cfg.GetResolved("A");

API Reference

Extension Methods

MethodDescription
GetResolved(key)Get resolved configuration value
GetResolved<T>(key)Get resolved value with type conversion
GetResolved(key, options)Get resolved value with custom options
TryGetResolved(key, out value)Try to get resolved value
TryGetResolved<T>(key, out value)Try to get resolved value with type conversion
GetManyResolved(keys)Batch get resolved values
ResolveVariables(template)Resolve variables in template string
ResolveVariables(template, options)Resolve template with custom options

VariableResolutionOptions Properties

PropertyTypeDefaultDescription
VariablePrefixstring"${"Variable prefix
VariableSuffixstring"}"Variable suffix
PrefixSeparatorstring":"Prefix separator
MaxRecursionDepthint10Maximum recursion depth
UnresolvedBehaviorenumKeepHow to handle unresolved variables
CacheResultsbooltrueWhether to cache results
InvalidateCacheOnChangebooltrueClear cache on config change
ResolversIListBuilt-in resolversVariable resolver list

Built-in Resolvers

ResolverPrefixDescription
VariableResolvers.ConfigNoneReference other config keys
VariableResolvers.EnvironmentENVReference environment variables
VariableResolvers.SystemSYSReference system properties

Released under the MIT License