Provisioning: no supported way to bind a ParameterResource to a non-string bicep parameter (int/bool)
- 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
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