microsoft / microsoft/aspire

Provisioning: no supported way to bind a ParameterResource to a non-string bicep parameter (int/bool)

Open
#19,492 0 comments 0 reactions 0 assignees View on GitHub
area-app-model
Dominant language
C#
Stars
6.3k
Forks
991
Avg merge
2d 15h
Merged PRs (30d)
196

Description

## Summary

`AsProvisioningParameter` is the canonical way to bind an Aspire parameter into an Azure provisioning resource, but it can only ever produce a `string` bicep parameter. There is currently no supported path for parameterizing a bicep parameter that must be typed `int` or `bool`.

This is fine for the common cases in the repo today (names, locations, address prefixes, custom domains, credentials — all strings), but it becomes a real gap for integrations that want to parameterize things like instance counts, capacities, replica counts, or feature toggles.

## Detail

`GetOrAddParameter` unconditionally declares `typeof(string)`:

```csharp
// src/Aspire.Hosting.Azure/AzureProvisioningResourceExtensions.cs
private static ProvisioningParameter GetOrAddParameter(AzureResourceInfrastructure infrastructure, string parameterName, bool? isSecure = null)
{
var parameter = infrastructure.GetParameters().FirstOrDefault(p => p.BicepIdentifier == parameterName);
if (parameter is null)
{
parameter = new ProvisioningParameter(parameterName, typeof(string));
...
}
return parameter;
}
```

That is consistent with `ParameterResource`, whose value is always a string at the manifest level — there is no `ParameterResource` or typed variant. `EnsureParametersAlign` also back-fills with `typeof(string)`.

The result is that if a resource declares `param instanceCount int` and a user wants that value to come from an Aspire parameter, there is nothing in the public surface to express it. The only `typeof(int)` `ProvisioningParameter` in the repo (`capacity` in `AzureWebPubSubExtensions`) is a static default with a literal `Value` and is never fed from an Aspire parameter — users override it via `WithParameter("capacity", 2)` with an object literal, which is a different and non-composable path.

## What downstream integrations end up doing

A first-party Aspire integration hit this while adding per-region overrides for cluster settings (VM SKU, instance count, disk SKU, zone resiliency). SKU and disk type were straightforward strings; instance count (`int`) and zone resiliency (`bool`) were not. The workaround was to declare the bicep parameter as `string` so it can carry a `ParameterResource`, then coerce at the use site with the bicep intrinsics:

```csharp
// declare the module parameter as string so it can carry a ParameterResource...
var instanceCount = new ProvisioningParameter("vmInstanceCount", typeof(string));

// ...then coerce at the point of use to satisfy the int-typed property
BicepValue value = new FunctionCallExpression(
new IdentifierExpression("int"),
new IdentifierExpression("vmInstanceCount"));
```

and equivalently `bool(zoneResilient)` for a `bool` property, with `"true"` / `"false"` carried as strings.

This works, but it has some sharp edges:

- The module's parameter **type changes shape** depending on whether the caller opted into the parameterized overload (`int` in the default path, `string` when parameterized). That is awkward for anything consuming the generated bicep.
- Every use site of the parameter needs a matching coercion, and it is easy to miss one.
- Invalid values fail at deploy time rather than publish time, with a fairly opaque error.
- There is no `IdentifierExpression("int")` / `("bool")` precedent anywhere in this repo, so each integration that hits this invents its own convention.

## Possible directions

Not asking for anything specific — mostly flagging that the pattern is being reinvented downstream and might be worth generalizing. Some options:

1. **Typed parameters** — some form of `ParameterResource` that carries a CLR type, so `AsProvisioningParameter` can emit `param x int` / `param x bool` directly.
2. **An overload on `AsProvisioningParameter`** taking the desired bicep type, emitting the coercion (or a typed parameter) as an implementation detail.
3. **A first-class coercion helper** — e.g. `parameter.AsProvisioningParameter(infra).AsInt()` / `.AsBool()` — so at least the `int(...)` / `bool(...)` wrapping is centralized and consistent rather than hand-rolled per integration.

For now the downstream integration is going to live with the manual coercion; this issue is mainly to record the gap and the shape of the workaround in case it is worth pulling into the framework.

## Environment

Observed against `main` (commit `93b14b4`).

Contributor guide

Open the contributing guide

Research direction

Start in src/Aspire.Hosting.Azure/AzureProvisioningResourceExtensions.cs by reading GetOrAddParameter, EnsureParametersAlign, and AsProvisioningParameter, then compare the typed capacity handling in AzureWebPubSubExtensions. Done requires a decided public API for int and bool Aspire parameters, with corresponding coverage in the relevant Azure provisioning tests; the issue does not choose among its proposed directions.

Written by the indexing model from the issue text.

Assessment

Tech stack
azure, csharp
Domain
cloud, infrastructure
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.