Background and Motivation
Integrations that fetch secrets from external sources (e.g. Bitwarden Secrets Manager) need two capabilities that are currently missing from the public API:
-
Set a parameter value programmatically (obtained from a remote source) without showing a user interaction prompt. ParameterProcessor.SetParameterAsync always opens an interactive dialog; there is no non-interactive overload for automated provisioners.
-
Check whether a parameter already has a value before deciding to fetch from a remote. The only public method is GetValueAsync, which is async and blocks on WaitForValueTcs when the parameter is unresolved — so it can't be used as a synchronous "has value" probe. The internal ValueInternal property does exactly what's needed but isn't accessible outside the assembly.
There is no workaround for (1) that avoids ParameterProcessor.SetParameterAsync's mandatory interaction prompt.
The current workaround for (2) is to call GetValueAsync, check task.IsCompletedSuccessfully, and cancel the token — a brittle race that relies on undocumented synchronous-completion behavior:
// Workaround — fragile and relies on implementation details
using var cts = new CancellationTokenSource();
var task = parameter.GetValueAsync(cts.Token).AsTask();
bool hasValue = task.IsCompletedSuccessfully && !string.IsNullOrWhiteSpace(task.Result);
cts.Cancel();
Proposed API
namespace Aspire.Hosting;
[Experimental("ASPIREINTERACTION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")]
public sealed class ParameterProcessor
{
+ public Task SetValueAsync(ParameterResource parameterResource, string value, bool saveToUserSecrets = false, CancellationToken cancellationToken = default)
}
namespace Aspire.Hosting.ApplicationModel;
public class ParameterResource : Resource, IExpressionValue
{
+ public bool TryGetCurrentValue([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? value)
}
SetValueAsync applies the given value to the parameter (updating WaitForValueTcs and publishing resource state) without opening an interaction dialog. The optional saveToUserSecrets flag mirrors the existing ApplyParameterValueAsync behavior.
TryGetCurrentValue returns true and populates value when the parameter has a resolved, non-empty string available right now. It returns false when the parameter is unresolved (pending WaitForValueTcs) or when the resolved value is null/empty. It never blocks.
Usage Examples
// In a provisioner that resolves parameter values from a remote secret store:
async Task ProvisionAsync(ParameterResource parameter, IServiceProvider services, CancellationToken ct)
{
if (parameter.TryGetCurrentValue(out _))
{
// Already has a value — skip the remote fetch.
return;
}
string secretValue = await bitwardenStore.GetSecretAsync(parameter.Name, ct);
ParameterProcessor processor = services.GetRequiredService<ParameterProcessor>();
await processor.SetValueAsync(parameter, secretValue, cancellationToken: ct);
}
// Conditional prompt fallback — only ask the user if the remote had nothing:
if (!parameter.TryGetCurrentValue(out _))
{
string? remoteValue = await bitwardenStore.TryGetSecretAsync(parameter.Name, ct);
if (remoteValue is not null)
await processor.SetValueAsync(parameter, remoteValue, cancellationToken: ct);
else
await processor.SetParameterAsync(parameter, ct); // fall back to interactive prompt
}
Alternative Designs
For SetValueAsync:
- Add
SetValueAsync directly on ParameterResource — bypasses the notification and user-secrets machinery owned by ParameterProcessor, which would leave the dashboard state out of sync.
- A new
IParameterValueProvider abstraction — more extensible for IoC scenarios but significantly more ceremony for what is essentially a one-liner wrapper around existing internal logic.
- Overload
SetParameterAsync with a string? value parameter that skips the prompt when a value is supplied — avoids a new method name but conflates interactive and non-interactive paths in a single method.
For TryGetCurrentValue:
- Expose
ValueInternal as a public property — gives access to the value but leaks the Lazy<string> caching detail and doesn't communicate "no value yet" clearly.
- Add a
bool HasValue property with no out parameter — callers that want the value still need a second call to GetValueAsync, making the check non-atomic.
- Add
ValueTask<bool> HasValueAsync() — avoids the synchronous concern but forces async plumbing in callers that only need a quick check before kicking off a heavier async operation.
Risks
SetValueAsync bypasses the interactive confirmation flow. Callers take full responsibility for the value's correctness and security. Secret values should only be set over this API when the caller has already authenticated with the remote store.
- The
[Experimental] attribute on ParameterProcessor means SetValueAsync inherits that caveat; callers must suppress ASPIREINTERACTION001 until the API is stabilized.
TryGetCurrentValue must handle the three distinct internal states (WaitForValueTcs not set, set-but-not-completed, set-and-completed) and return false for all non-ready states. If the semantics aren't clearly specified, callers may accidentally check before InitializeParametersAsync has run and always see false.
Background and Motivation
Integrations that fetch secrets from external sources (e.g. Bitwarden Secrets Manager) need two capabilities that are currently missing from the public API:
Set a parameter value programmatically (obtained from a remote source) without showing a user interaction prompt.
ParameterProcessor.SetParameterAsyncalways opens an interactive dialog; there is no non-interactive overload for automated provisioners.Check whether a parameter already has a value before deciding to fetch from a remote. The only public method is
GetValueAsync, which is async and blocks onWaitForValueTcswhen the parameter is unresolved — so it can't be used as a synchronous "has value" probe. The internalValueInternalproperty does exactly what's needed but isn't accessible outside the assembly.There is no workaround for (1) that avoids
ParameterProcessor.SetParameterAsync's mandatory interaction prompt.The current workaround for (2) is to call
GetValueAsync, checktask.IsCompletedSuccessfully, and cancel the token — a brittle race that relies on undocumented synchronous-completion behavior:Proposed API
namespace Aspire.Hosting; [Experimental("ASPIREINTERACTION001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] public sealed class ParameterProcessor { + public Task SetValueAsync(ParameterResource parameterResource, string value, bool saveToUserSecrets = false, CancellationToken cancellationToken = default) }namespace Aspire.Hosting.ApplicationModel; public class ParameterResource : Resource, IExpressionValue { + public bool TryGetCurrentValue([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out string? value) }SetValueAsyncapplies the given value to the parameter (updatingWaitForValueTcsand publishing resource state) without opening an interaction dialog. The optionalsaveToUserSecretsflag mirrors the existingApplyParameterValueAsyncbehavior.TryGetCurrentValuereturnstrueand populatesvaluewhen the parameter has a resolved, non-empty string available right now. It returnsfalsewhen the parameter is unresolved (pendingWaitForValueTcs) or when the resolved value is null/empty. It never blocks.Usage Examples
Alternative Designs
For
SetValueAsync:SetValueAsyncdirectly onParameterResource— bypasses the notification and user-secrets machinery owned byParameterProcessor, which would leave the dashboard state out of sync.IParameterValueProviderabstraction — more extensible for IoC scenarios but significantly more ceremony for what is essentially a one-liner wrapper around existing internal logic.SetParameterAsyncwith astring? valueparameter that skips the prompt when a value is supplied — avoids a new method name but conflates interactive and non-interactive paths in a single method.For
TryGetCurrentValue:ValueInternalas a public property — gives access to the value but leaks theLazy<string>caching detail and doesn't communicate "no value yet" clearly.bool HasValueproperty with no out parameter — callers that want the value still need a second call toGetValueAsync, making the check non-atomic.ValueTask<bool> HasValueAsync()— avoids the synchronous concern but forces async plumbing in callers that only need a quick check before kicking off a heavier async operation.Risks
SetValueAsyncbypasses the interactive confirmation flow. Callers take full responsibility for the value's correctness and security. Secret values should only be set over this API when the caller has already authenticated with the remote store.[Experimental]attribute onParameterProcessormeansSetValueAsyncinherits that caveat; callers must suppressASPIREINTERACTION001until the API is stabilized.TryGetCurrentValuemust handle the three distinct internal states (WaitForValueTcsnot set, set-but-not-completed, set-and-completed) and returnfalsefor all non-ready states. If the semantics aren't clearly specified, callers may accidentally check beforeInitializeParametersAsynchas run and always seefalse.