DKNet

DKNet.Svc.Transformation

Fills bracketed tokens in a template string — [Name], {Email}, <Amount>, {{Ref}} — from plain objects or string dictionaries, formatting each resolved value on the way in.

✨ Why use it?

🚀 Quick Start

Use a .NET 10 console project. Install the package, then replace Program.cs with the example.

dotnet new console -f net10.0 -n TransformDemo
cd TransformDemo
dotnet add package DKNet.Svc.Transformation
dotnet add package Microsoft.Extensions.DependencyInjection
using Microsoft.Extensions.DependencyInjection;
using DKNet.Svc.Transformation;

var services = new ServiceCollection();
services.AddTransformerService(); // ITransformerService, transient
var provider = services.BuildServiceProvider();

var transformer = provider.GetRequiredService<ITransformerService>();
var order = new { OrderId = 4711, Total = 129.5m };
Console.WriteLine(transformer.Transform("Order [OrderId] — [Total]", order));
// Order 4,711 — 129.50

Run with dotnet run.

🏗️ Architecture

The host application configures AddTransformerService once, which registers TransformOptions and a transient TransformerService; each call reads TokenExtractor instances, TokenResolver and IValueFormatter.

AddTransformerService returns immediately if ITransformerService is already registered, so only the first call’s options take effect. TransformerService is transient, TransformOptions is held as a singleton IOptions<TransformOptions>. This does not show request scoping in the host app, or that TokenExtractor/TokenResolver are internal types the service builds for itself.

🔄 How it works

The template string is scanned by one extractor per token definition, producing tokens with a key and an index; TokenResolver looks each key up against the call's parameters and then the global parameters, taking the first non-null value; resolved values go through IValueFormatter and unresolved ones through the not-found policy, and both feed the filled string that is rebuilt in a single pass.

Transform runs extraction, resolution, formatting, and rebuilding synchronously; TransformAsync differs only in that extraction runs on the thread pool — resolution itself is always synchronous reflection. This does not show the per-call token cache (one dictionary created fresh inside each Transform/TransformAsync call, so a token repeated in the same template resolves once and nothing ever leaks between calls), or that the default TokenNotFoundBehavior is ThrowError.

🧩 Features