redb.Route.Xml 4.0.1

Prefix Reserved
dotnet add package redb.Route.Xml --version 4.0.1
                    
NuGet\Install-Package redb.Route.Xml -Version 4.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="redb.Route.Xml" Version="4.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="redb.Route.Xml" Version="4.0.1" />
                    
Directory.Packages.props
<PackageReference Include="redb.Route.Xml" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add redb.Route.Xml --version 4.0.1
                    
#r "nuget: redb.Route.Xml, 4.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package redb.Route.Xml@4.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=redb.Route.Xml&version=4.0.1
                    
Install as a Cake Addin
#tool nuget:?package=redb.Route.Xml&version=4.0.1
                    
Install as a Cake Tool

redb.Route.Xml

Declarative XML routes for redb.Route: .route.xml documents load into the existing fluent DSL — one engine, no second semantics. Every element is a thin facade over exactly one DSL verb; whatever the XML can say, C# can say, and the two spellings produce byte-identical definition trees (the test suite proves it for every shipped example).

What you get on top of the engine:

  • the full EIP vocabulary as XML — ~60 elements covering every string-expressible verb of the DSL, plus container-level handlers and the REST DSL;
  • load-time failure for everything that can fail early: unknown elements (with a did-you-mean hint), malformed expressions, unregistered URI schemes, attribute typos — all collected in one pass with file(line,column) positions;
  • a generated XSD for editor autocompletion (VS Code / Visual Studio / Rider, no plugin of ours), a component catalog for property panels, a C# generator and Mermaid diagrams from the same parse;
  • project scaffolding and packaging: redb-route-xml new → an ordinary .csproj with routes, resources and layered configuration; pack → a signed-ready package layout with a manifest and a hard-error check gate.

Quick start

await using var context = new RouteContext()
    .AddXmlRoutesFromContent("""
        <routes xmlns="urn:redb:route:1.0">
          <route id="orders-in" description="Orders intake">
            <from uri="direct://orders"/>
            <setHeader name="priority" expr="${header.amount > 1000 ? 'high' : 'normal'}"/>
            <filter expr="header.amount > 0">
              <choice>
                <when expr="header.priority == 'high'">
                  <to uri="direct://orders-vip"/>
                </when>
                <otherwise>
                  <to uri="direct://orders-std"/>
                </otherwise>
              </choice>
            </filter>
          </route>
        </routes>
        """);
await context.Start();

Files and globs work the same way — AddXmlRoutes("routes/*.route.xml") loads every match in deterministic order (relative paths resolve through the route resource resolver, never the process working directory), and a pattern matching nothing is an error naming the searched places. In a DI host, register inside AddRedbRoute:

services.AddRedbRoute(r => r.AddXmlRoutes("routes/*.route.xml"));

A document with errors registers nothing — there is no half-loaded state.

The format at a glance

The namespace is urn:redb:route:1.0; the major part changes only on a breaking format change, and a document carrying a newer minor is refused with "update the redb.Route.Xml package" rather than a confusing parse error. Every element lives in this namespace — a foreign-namespace element never passes just because its local name matches.

Every element accepts id= and description=: they flow into step identity (message history node ids and labels, WeaveById targets, generated-code comments).

Leaf stepsto, toD, setHeader, setProperty, setBody, setHeaders, transform, removeHeader/Property/Body/Headers/Properties, log, delay, stop, throwException, convertBody, wireTap, validate, sort, sample, streamCaching, validateJsonSchema, validateXsd, xslt, marshal, unmarshal, controlBus, enrich, pollEnrich, recipientList, dynamicRouter, routingSlip, claimCheck, beginTransaction, commitTransaction, rollbackTransaction, rollbackAll, exceptionHandled, routePolicy, saga, scatterGather, loadBalance, normalize.

Scopes (children are steps) — filter, split (with tokenizeLines / tokenizeXml / tokenizeJsonArray children), multicast, aggregate, tryCatch (try/catch/finally), loop (count / expr / while), throttle (plain and keyed), debounce, circuitBreaker (with fallback), idempotentConsumer, resequence, transaction, traced, metered, replayable, threads, ofType, onException, intercept, interceptFrom, interceptSendToEndpoint, onCompletion.

Branchingchoice with when (expression or predicate="#name" registry reference) and otherwise.

Container level (directly under <routes>, applying to every route of the file) — bean, onException, intercept, interceptFrom, interceptSendToEndpoint, onCompletion, and package-contributed top-level elements such as rest.

A few rules that keep documents unambiguous:

  • value= is a constant, expr= is an expression — both at once is a schema error, and which one you used decides the meaning, never the content of the string;
  • file-or-content elements (xslt, validateXsd, validateJsonSchema, transformJson, payload) take file=/locator or inline content (CDATA welcome) — not both;
  • <aggregate> requires an explicit strategy — there is no silent default;
  • a ${…} placeholder in a consumer URI (<from>) is refused at load: there is no message to resolve it against.

Expressions

Positions decide meaning — the same rule the fluent DSL follows:

Position Elements Becomes
condition filter, when, validate, loop while= a predicate
value setBody, setHeader, transform, toD, keys and correlations an evaluated object
plain string uri, id, description, name, literal value= a literal, never parsed

The expression language is the engine's own — ${header.x}, count(property.items) > 2 ? 'many' : 'few', ${jpath('$.order.id')}, ${xpath('/order/@id')}, arithmetic, uuid(), datediff(...), messageHistory(...) (the exchange's own trail: 'compact', 'count', 'slowestMs', ...), stats(...) (endpoint statistics, and the OpenTelemetry layer through 'otel:<instrument>') — and a broken expression fails when the document loads, not on the first message. Whitespace around operators is insignificant, so expr="header.amount>1000" avoids XML escaping entirely (> needs no escape in attribute values; < does).

Type-shaped checks the language cannot express go through the registry: <when predicate="#isStringList"> resolves an IPredicate you registered from code.

Endpoints: URI form and structured form

Short addresses stay URIs. Long or option-heavy ones can be written structurally — the loader normalizes the element into the same URI string before any endpoint exists, so the whole engine pipeline (endpoint cache, statistics, mock masks, secret redaction) sees one canon:

<to uri="kafka://orders?key=${header.tripId}"/>          
<to>
  <kafka topic="orders" key="${header.tripId}" groupId="orders-svc"/>
</to>

<to>
  <sql dataSource="#main-db">
    <![CDATA[ INSERT INTO auth_log(login, at) VALUES (:#login, :#at) ]]>
    <param name="login" value="${header.login}"/>
    <param name="at" value="${dateformat(now(), 'o')}"/>
  </sql>
</to>

The rules are generic — no connector has XML-specific code:

  • the element name is the scheme; every attribute becomes a query option verbatim;
  • the path part is the universal path= attribute, the component's declared one-line synonym (kafkatopic, filedirectory), or the element's text content for text-path components (sql — and path= on those is refused: the query belongs in CDATA);
  • <param name="…" value="…"/> children become param.name=… family options; a child with content and no name becomes a long text option (<onSuccess><![CDATA[…]]></onSuccess>);
  • connection factories are ordinary options carrying registry references: connectionFactory="#mainKafka", dataSource="#main-db".

Container level: beans, handlers, REST

<bean> declares objects in the context registry — the factories your endpoints reference:

<bean name="main-db" type="redb.Route.Sql.Connection.SqlConnectionFactory, redb.Route.Sql">
  <constructorArg>
    <bean type="redb.Route.Sql.Connection.SqlConnectionOptions, redb.Route.Sql">
      <property key="ConnectionString" value="{{db.main.connection}}"/>
    </bean>
  </constructorArg>
</bean>

Values resolve {{key}} / {{key:default}} through the context's configuration chain (IConfiguration, then context properties) before type conversion — {{ldap.port:636}} binds to an int. Nested anonymous beans build option graphs inline. Objects with real dependencies stay in code; XML references them by #name.

A property takes either value= or one nested anonymous <bean>, so a factory that holds an OBJECT - a certificate, a credentials object, a serializer - is declarable too. When the type is built by a static creator rather than a constructor, factoryMethod= names it and the <constructorArg> values are its arguments:

<bean name="globex" type="redb.Route.As2.As2ConnectionFactory, redb.Route.As2">
  <property key="OurCertificate">
    <bean type="System.Security.Cryptography.X509Certificates.X509CertificateLoader, System.Security.Cryptography.X509Certificates"
          factoryMethod="LoadPkcs12FromFile">
      <constructorArg value="{{as2.certificates}}/hub.pfx"/>
      <constructorArg value="{{as2.password}}"/>
    </bean>
  </property>
  <property key="As2From" value="{{as2.id}}"/>
  <property key="Sign" value="true"/>
</bean>

Handlers declared at the container level apply to every route of the file:

<onException exceptions="System.Exception" handled="true"
             maximumRedeliveries="2" redeliveryDelay="00:00:01" exponentialBackOff="true">
  <log level="Error">[ERR] ${routeId}: ${exception.message}</log>
</onException>

The REST DSL is a container-level element from the redb.Route.Http package:

<rest path="/api/orders" port="5099" bindingMode="json">
  <get path="/{id}" id="orders-get" to="direct://orders-get-handler"/>
  <post produces="application/json">
    <setHeader name="accepted" value="true"/>
    <to uri="kafka://orders"/>
  </post>
</rest>

A verb handles its request with to= or inline steps — one of the two. Path parameters arrive as header.id, query as header.query.*; OpenAPI is served at {path}/openapi.json.

The context document

Besides route files, a package can carry a context.xml — the context-level concerns:

<context xmlns="urn:redb:route:1.0">
  <components>
    <component type="redb.Route.Sql.SqlComponent, redb.Route.Sql"/>
  </components>
  <bean name="main-db" type="…"> … </bean>
  <onInit>
    <to uri="sql:CREATE TABLE IF NOT EXISTS demo_log (id BIGINT)?dataSource=#main-db"/>
    <to uri="bean:#seeder?method=EnsureDefaults"/>
  </onInit>
</context>

<onInit> is a pipeline of ordinary format steps, run once in the engine's fail-fast bootstrap phase: a failed step keeps the context from accepting traffic, and the error names the source file. Load it with AddXmlContext("context.xml") / AddXmlContextFromContent(...) — before the route files, so #references and schemes resolve.

Configuration and enabled=

Configuration is layered; the package itself carries identity only (ContextName, AutoStart) — settings and secrets arrive from the host's merged configuration at deploy time. Every {{key}} without a {{key:default}} is recorded by the packaging tool in the manifest's RequiredConfigKeys, so a missing value fails fast at module init instead of surfacing as a malformed URI at 3 a.m.

enabled="{{features.orders:false}}" on a <route> resolves from the same chain at load — a route can be switched per environment without touching the package. enabled="false" skips registration entirely (stronger than autoStart="false", which registers but does not start).

Errors: one pass, positions, no half-states

Everything wrong with a document is reported together:

routes/orders.route.xml(4,6): unknown element <setHeaderr>. Did you mean <setHeader>?
routes/orders.route.xml(7,10): <setHeader> takes 'value' or 'expr', not both.
routes/orders.route.xml(12,8): [schema] The 'bogus' attribute is not allowed.
routes/orders.route.xml(15,6): scheme 'kafkaa' is not registered in this context.

The parser's own findings win on a shared line; the generated-XSD pass (on by default, ValidateAgainstSchema = false to opt out) adds what the parser deliberately leaves to it — above all, attribute typos. Fixing ten mistakes takes one run, not ten.

Testing an XML route

An XML route is tested exactly like a C# route — with redb.Route.TestKit, no test host of its own:

await using var ctx = new RouteContext().AddXmlRoutesFromContent(xml);
ctx.AdviceAllRoutes(a => a.MockEndpoints("kafka://*", "sql:*"));
await ctx.Start();

var mock = ctx.Mock("kafka://orders").ExpectMessageCount(1).ExpectHeader("seen", "true");
await ctx.SendBody("direct://in", "payload");
await mock.AssertIsSatisfiedAsync(TimeSpan.FromSeconds(2));
  • Swapping transports. MockEndpoints("kafka://*") rewrites the definition tree between load and Start() — the same seam for both spellings of a route.
  • Weaving by id. AdviceRoute(id, a => a.WeaveById("step-id").Replace(...)) finds a step by its XML id= attribute.
  • Secrets stay out of reports. The query string of a URI — where passwords live — is not part of the mock name and never appears in a failed assertion's report (nor, from there, in CI logs).
  • Why mock: and not a real broker. A unit test asserts the ROUTE — branching, headers, bodies; a broker adds latency and flakiness to that claim. Keep one integration test per transport against the real thing; let every route test run on mocks.
  • Structural comparison. redb.Route.Diagnostics.RouteDescriber renders a definition tree as stable, diffable text — equivalence tests compare XML against its fluent C# twin byte-for-byte, and golden files pin the parse of every shipped example.

Schema and editor support

The XSD is generated from the element registry — the same list the parser reads, so they cannot drift. XmlRouteSchema.Generate(registry) covers the vocabulary with enum value hints (level=, policy=, strategy= complete from a list), required attributes, and rejection of unknown elements; foreign-namespace attributes are tolerated by design so other tools can annotate route files. Pass a ComponentCatalog to make structured endpoint children strict and typed per connector. Point any XSD-aware editor at it — VS Code with the RedHat XML extension:

// .vscode/settings.json
{
  "xml.fileAssociations": [
    { "pattern": "**/*.route.xml", "systemId": "./schema/redb-route-1.0.xsd" }
  ]
}

The component catalog (ComponentCatalog.Build, ToJson) describes every connector for a property panel — scheme, path synonym, options with types, defaults, enum values, [Sensitive] marks — by reflection over the connector's own Options class. A new connector appears in the structured form, the catalog and the schema with zero XML-specific code.

Tooling: scaffold, pack, generate

The redb-route-xml dotnet tool (package redb.Route.Xml.CodeGen — a developer tool, not part of the runtime):

Command What it does
new Orders --context orders scaffolds a route project: an ordinary .csproj, context.xml, routes/, resources/, layered config/, the generated XSD wired into .vscode
pack . --version 1.0.0 [--bin bin/Debug/net9.0] runs the checks, then builds the package layout (manifest.json, config, artifacts, resources) and a .tpkg zip
check . the same gate for CI — findings only
catalog <binDir> --out dir builds the component catalog + catalog-aware XSD from built connector assemblies
csharp routes/x.route.xml --namespace My.Routes fluent C# (readable, or --style machine with #line mapping diagnostics back to the XML)
mermaid routes/x.route.xml a flowchart from the same parse
xsd --out dir the generated schema for the core set

Packaging checks are hard errors, not advice: schema validation with positions, ]]> inside CDATA, file= resources missing from resources/, undeclared #name references; with --bin, bean types are verified against the real assemblies — a renamed type, a non-public type, a typo in a <property> or in bean:…?method= refuses the build. A literal secret in a URI is a warning that names the fix (supply it through configuration). Add -p:PackRouteOnBuild=true to a scaffolded project to pack on every dotnet build.

About the generated C#: it is what the XML says, pronounced in C# — not what a person would have written. There are no lambdas in it, because there are none in XML. For migration that is exactly right; the mechanical guarantee is stronger than style — the generated code produces a definition tree byte-identical to loading the XML.

Extending the format from your package

A package with DSL of its own ships one class per element — parse, schema shape and C# printing together, so the loader, the XSD and the generator see the element together or not at all:

public sealed class CacheXmlContribution : IXmlElementContribution
{
    public string Name => "cache";
    public XmlElementKind Kind => XmlElementKind.Scope;
    public ElementSpec Spec => …;          // attributes, enum values, children — feeds the XSD
    public IRouteDefinition Apply(…) => …; // calls the package's own fluent DSL, nothing else
    public void Print(…) => …;             // the generator's face; missing = loud error
}

Hosts register contributions via XmlRouteLoaderOptions.Extensions. A duplicate element name is a hard error, never a silent override. Container-level elements (like <rest>) implement IXmlTopLevelContribution and apply to the route builder. Shipping today: <cache> (redb.Route.Cache), <transformJson> (redb.Route.JsonTransform), <payload> (redb.Route.Templates), <rest> (redb.Route.Http).

What XML deliberately does not do

Lambdas, custom processors' logic, complex seeding — code. XML references it (bean:#name, predicate="#name", <saga><step processor="#name"/>), never embeds it: there is no compiler in the runtime and no second trust contour — a package's code is signed and verified before anything runs, and the XML only chooses which already-trusted types to use.

The schema of the format is generated from the element registry: redb-route-xml xsd <bin directory> writes it for exactly the packages in that directory, their contributed elements included, and the VS Code extension validates against it. See XML Route Tools.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (6)

Showing the top 5 NuGet packages that depend on redb.Route.Xml:

Package Downloads
redb.Route.Http

HTTP transport for redb.Route ESB framework. Provides HttpClient producer and Kestrel-based consumer (webhook receiver) with CORS, auth, and streaming support.

redb.Route.Core

Bridge package connecting redb.Route ESB framework with redb.Core typed props storage. Provides RedbIdempotentRepository, typed access to IRedbService from route pipelines, and extension methods for redb.Core integration.

redb.Tsak.Core

Kernel of redb.Tsak — runtime container for redb.Route contexts. Provides hot-reload module loading, REST management API, scheduler, monitoring, security and pluggable cluster bootstrap.

redb.Route.Cache

Cache as an EIP for redb.Route: a caching scope (.Cache(key, ttl) ... .EndCache()) and a cache: component (get/put/remove/clear) over IMemoryCache or IDistributedCache.

redb.Route.Templates

Payload templates for redb.Route (WSO2 PayloadFactory / Camel templating analog): Scriban templates from files, embedded resources or inline text, media-type aware escaping (JSON / XML / text), named arguments from route expressions.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.0.1 38 9/18/2026
4.0.0 299 9/11/2026