Skip to content

Support Azure Functions %value% binding expressions in trigger attributes#80

Merged
danielmarbach merged 3 commits intomainfrom
value_binding_support
Apr 28, 2026
Merged

Support Azure Functions %value% binding expressions in trigger attributes#80
danielmarbach merged 3 commits intomainfrom
value_binding_support

Conversation

@danielmarbach
Copy link
Copy Markdown
Contributor

@danielmarbach danielmarbach commented Apr 28, 2026

The NServiceBus.AzureFunctions source generator extracts literal strings from [ServiceBusTrigger] attributes at compile time, for example, "%billingPrefix%-api", and stores them as-is in FunctionManifest. At runtime, OverrideLocalAddress("%billingPrefix%-api") and transport.ConnectionName = "%MyConnection%" are called with unresolved %value% patterns, which breaks the endpoint.

How %value% works in Azure Functions

The %SettingName% syntax is an Azure Functions Host convention. The Host resolves these patterns against app settings before data reaches the worker process. Extensions with [SupportsDeferredBinding] receive already-resolved values via ModelBindingData.Content. The attribute instance on the worker method (via reflection) still contains raw %value% strings, but those extensions never read from it.

NServiceBus.AzureFunctions doesn't use SupportsDeferredBinding for its trigger configuration. It needs the queue name and connection setting at startup time to configure the NServiceBus transport before any messages are received. So it can't rely on the Host's runtime resolution.

Chosen approach: Resolve %value% from IConfiguration at startup

The isolated worker runs in the same process as the Host. FunctionsApplicationBuilder.Configuration (IConfiguration) contains all the same settings the Host has environment variables, Key Vault references, App Configuration providers, etc. There should be no mismatch scenario.

FunctionBindingExpression.Resolve(value, configuration) uses [GeneratedRegex("%([^%]+)%")] to find all %...% tokens and resolves each against IConfiguration. It supports:

  • Simple tokens: %myQueue%orders-queue
  • Embedded tokens: %billingPrefix%-apibilling-api
  • Multiple tokens: %prefix%-%env%-%name%myapp-prod-orders
  • Nested config sections: %ServiceBus:QueueName% and %ServiceBus__QueueName%
    Resolution is applied in two places:
  • FunctionEndpointConfigurationBuilder: resolves functionManifest.Address before calling OverrideLocalAddress
  • AddNServiceBusAzureServiceBusFunction: resolves functionManifest.ConnectionSettingName before setting transport.ConnectionName

Why not deferred/late resolution?

Deferred endpoint start (resolve queue name on first invocation from BindingData or trigger metadata) was considered but rejected because:

  • NServiceBus configures and starts the endpoint during DI setup (AddNServiceBusAzureServiceBusFunction). Restructuring this to be lazy would be architecturally invasive: the entire transport, messaging pipeline, and receive infrastructure is initialized eagerly, which is something we did in the previous function package and we don't necessarily want to go back to this unless really required.
  • The first invocation would arrive before the endpoint is ready, creating a race condition.
  • IConfiguration is available at startup and contains all the same values the Host would resolve, making early resolution equivalent and simpler.

Why not "just don't support it"?

Limits configurability for a pattern that Azure Functions users commonly expect. Users who want different queue names per environment (dev/staging/prod) shouldn't need different compiled binaries; %value% seems to be the idiomatic way.

…inding expressions and update APIs to use it
…ngs and update documentation with configuration details for local testing
Comment thread .github/workflows/ci.yml Outdated
@danielmarbach danielmarbach changed the title Value binding support Support Azure Functions %value% binding expressions in trigger attributes Apr 28, 2026
@danielmarbach danielmarbach requested review from DavidBoike, andreasohlund and mattmercurio and removed request for DavidBoike April 28, 2026 09:21
@danielmarbach danielmarbach marked this pull request as ready for review April 28, 2026 09:21
Comment on lines +8 to +11
public static class FunctionBindingExpression
{
public static string Resolve(string value, Microsoft.Extensions.Configuration.IConfiguration configuration) { }
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was going to ask if this needed to be public, but then I saw the usage in the ASB-specific component, so nevermind.

@danielmarbach danielmarbach merged commit 7344942 into main Apr 28, 2026
5 of 6 checks passed
@danielmarbach danielmarbach deleted the value_binding_support branch April 28, 2026 20:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants