Skip to content

Conversation

@sebastienros
Copy link
Member

@sebastienros sebastienros commented Oct 10, 2025

Description

  • Connection info annotations: WithReference now honors a new ReferenceEnvironmentInjectionAnnotation, letting resources opt into injecting connection strings, individual connection properties, or both via ReferenceEnvironmentInjectionFlags. Project, executable resources, Python and NpmApp default to injecting everything; others stay connection-string only unless annotated.
  • Reference expressions with formatting: ReferenceExpression get a format specifier (currently :uri) on both literals and resource providers. A new encoder helper ensures URI-safe interpolation without double encoding. The manifest expressions are not encoded but the code is commented for reference.
  • Connection property splatting: IResourceWithConnectionString exposes GetConnectionProperties, and ResourceBuilderExtensions now uses it to splat environment variables (with configurable prefixes) and to query specific properties via GetConnectionProperty.
  • Resource enrichments: A wide range of server/database resources (MySQL, PostgreSQL, SQL Server, MongoDB, Oracle, RabbitMQ, Redis/Valkey, Kafka, NATS, Garnet, Milvus, Qdrant, Seq, etc.) now populate detailed connection properties—host, port, usernames, URIs, JDBC strings, and more—to feed the new splatting behavior.
  • AppHost sample updates: The playground/node sample was updated to use the new connection metadata.

I used this document to define which properties need to be exposed and when UriExpression make sense:
connection-reference-pre-lastcols.md

Contributes to #2111

The Azure resources will be handled in a subsequent PR.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?
    • Yes - TBD
    • No

@github-actions
Copy link
Contributor

github-actions bot commented Oct 10, 2025

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 11938

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 11938"

@github-actions github-actions bot added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Oct 10, 2025
@sebastienros sebastienros changed the title Sebros/properties Implement Connection Properties and supporting expressions Oct 10, 2025
/// <param name="qdrantResource">The Qdrant server resource</param>
/// <param name="connectionName">An override of the source resource's name for the connection string. The resulting connection string will be "ConnectionStrings__connectionName" if this is not null.</param>
/// <returns>The <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<TDestination> WithReference<TDestination>(this IResourceBuilder<TDestination> builder, IResourceBuilder<QdrantServerResource> qdrantResource, string? connectionName = null)
Copy link
Member Author

Choose a reason for hiding this comment

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

@davidfowl this is an important change, this resource has a custom WithReference that didn't add the relationship. Now I am invoking the common one.

// Renders a custom operation in the manifest expression to indicate that
// the value should be URI-encoded.

// if (expression.StartsWith('{') &&
Copy link
Member Author

Choose a reason for hiding this comment

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

This is where the manifest could be updated with the uri formatting information.

/// <param name="resource">The resource that provides the connection properties. Cannot be null.</param>
/// <param name="key">The key of the connection property to retrieve. Cannot be null.</param>
/// <returns>The value associated with the specified connection property key.</returns>
public static ReferenceExpression GetConnectionProperty(this IResourceWithConnectionString resource, string key)
Copy link
Member

@davidfowl davidfowl Oct 11, 2025

Choose a reason for hiding this comment

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

Maybe this should be on the interface?

certKeyFile: process.env['HTTPS_CERT_KEY_FILE'] ?? '',
cacheAddress: process.env['ConnectionStrings__cache'] ?? '',
cacheUrl: process.env['CACHE_URI'] ?? '',
apiServer: process.env['services__weatherapi__https__0'] ?? process.env['services__weatherapi__http__0']
Copy link
Member

Choose a reason for hiding this comment

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

We gotta work on this next 😄

@davidfowl
Copy link
Member

davidfowl commented Oct 11, 2025

@aaronpowell big changes for polyglot connection strings. This is non breaking but we'll need a similar update on the community toolkit.

@sebastienros Lets generate a spec as part of this PR that explains the well known keys by convention:

  • Host, Port, Uri, JdbcConnectionString etc.

@sebastienros sebastienros requested a review from eerhardt October 13, 2025 21:47
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements a comprehensive connection properties system that allows resources to expose structured metadata (host, port, credentials, URIs, etc.) beyond just raw connection strings. Resources can now opt into injecting individual connection properties as environment variables, giving consuming applications more granular access to connection information.

  • Connection Properties API: New GetConnectionProperties() method on IResourceWithConnectionString exposes key-value pairs for structured connection metadata
  • Environment Injection Control: New annotation system (ReferenceEnvironmentInjectionFlags) allows resources to specify whether they want connection strings, individual properties, or both when referenced
  • URI Format Support: ReferenceExpression now supports :uri formatting to ensure proper URL encoding of interpolated values

Reviewed Changes

Copilot reviewed 62 out of 62 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Aspire.Hosting/ApplicationModel/IResourceWithConnectionString.cs Added GetConnectionProperties() method to interface
src/Aspire.Hosting/ApplicationModel/ReferenceExpression.cs Added format specifier support (:uri) and Empty static field
src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionFlags.cs New enum for controlling what gets injected as environment variables
src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionAnnotation.cs New annotation to configure injection behavior per resource
src/Aspire.Hosting/ResourceBuilderExtensions.cs Updated WithReference to support connection properties splatting
src/Aspire.Hosting/ConnectionPropertiesExtensions.cs Helper methods for combining connection properties
Multiple resource files (Redis, PostgreSQL, MySQL, etc.) Implemented connection properties for database and messaging resources
src/Aspire.Hosting.NodeJs/NodeExtensions.cs Added default All injection flags for NPM apps
Test files Comprehensive test coverage for new functionality
Comments suppressed due to low confidence (1)

tests/Aspire.Hosting.Tests/WithReferenceTests.cs:1

  • [nitpick] Consider using a more secure test password or avoiding special characters in test passwords that could cause encoding issues. The '@' symbol in passwords requires careful handling in URI encoding scenarios.
// Licensed to the .NET Foundation under one or more agreements.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
/// <returns>An enumerable collection of key/value pairs, where each key is the name of a connection property and each value
/// is its corresponding <see cref="ReferenceExpression"/>. The collection is empty if there are no connection
/// properties.</returns>
IEnumerable<KeyValuePair<string, ReferenceExpression>> GetConnectionProperties() => [];
Copy link
Member

@eerhardt eerhardt Oct 13, 2025

Choose a reason for hiding this comment

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

One nice thing about using a Dictionary here is that we can initialize it with a comparer. The design doc says

Keys are emitted in PascalCase; lookups are case insensitive and duplicates should be avoided.

But there is no real way to enforce "lookups are case insensitive and duplicates should be avoided".

But I agree that just doing IEnumerable will work fine, and should be lower overhead than a Dictionary.

Copy link
Member

Choose a reason for hiding this comment

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

We can always reconsider after some usage

/// <param name="source">The resource that exposes the base connection properties.</param>
/// <param name="additional">The additional connection properties to merge into the values supplied by <paramref name="source"/>.</param>
/// <returns>A sequence that contains the combined set of connection properties with duplicate keys resolved in favor of <paramref name="additional"/>.</returns>
public static IEnumerable<KeyValuePair<string, ReferenceExpression>> CombineProperties(this IResourceWithConnectionString source, IEnumerable<KeyValuePair<string, ReferenceExpression>> additional)
Copy link
Member

Choose a reason for hiding this comment

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

This method is going to show up in intellisense everywhere, right? Should we instead not make it an extension method?

Copy link
Member Author

Choose a reason for hiding this comment

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

More like a utility method? Can you think of another example so I move it these? I think it should remain public since we expect other integrations to reuse it.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, public is appropriate. Just not an extension method. I don't think the method needs to be moved necessarily.

Other examples of this is creating generated parameters -

public static ParameterResource CreateDefaultPasswordParameter(
IDistributedApplicationBuilder builder, string name,
bool lower = true, bool upper = true, bool numeric = true, bool special = true,
int minLower = 0, int minUpper = 0, int minNumeric = 0, int minSpecial = 0)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);
var generatedPassword = new GenerateParameterDefault
{
MinLength = 22, // enough to give 128 bits of entropy when using the default 67 possible characters. See remarks in GenerateParameterDefault
Lower = lower,
Upper = upper,
Numeric = numeric,
Special = special,
MinLower = minLower,
MinUpper = minUpper,
MinNumeric = minNumeric,
MinSpecial = minSpecial
};
return CreateGeneratedParameter(builder, name, secret: true, generatedPassword);
}
/// <summary>
/// Creates a new <see cref="ParameterResource"/> that has a generated value using the <paramref name="parameterDefault"/>.
/// </summary>
/// <remarks>
/// The value will be saved to the app host project's user secrets store when <see cref="DistributedApplicationExecutionContext.IsRunMode"/> is <c>true</c>.
/// </remarks>
/// <param name="builder">Distributed application builder</param>
/// <param name="name">Name of parameter resource</param>
/// <param name="secret">Flag indicating whether the parameter should be regarded as secret.</param>
/// <param name="parameterDefault">The <see cref="GenerateParameterDefault"/> that describes how the parameter's value should be generated.</param>
/// <returns>The created <see cref="ParameterResource"/>.</returns>
public static ParameterResource CreateGeneratedParameter(
IDistributedApplicationBuilder builder, string name, bool secret, GenerateParameterDefault parameterDefault)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentNullException.ThrowIfNull(name);
var parameterResource = new ParameterResource(name, defaultValue => GetParameterValue(builder.Configuration, name, defaultValue), secret)
{
Default = parameterDefault
};
if (builder.ExecutionContext.IsRunMode && builder.AppHostAssembly is not null)
{
parameterResource.Default = new UserSecretsParameterDefault(builder.AppHostAssembly, builder.Environment.ApplicationName, name, parameterResource.Default);
}
return parameterResource;
}
/// <summary>
/// Creates a new <see cref="ParameterResource"/>.
/// </summary>
/// <remarks>
/// The value will be saved to the app host project's user secrets store when <see cref="DistributedApplicationExecutionContext.IsRunMode"/> is <c>true</c>.
/// </remarks>
/// <param name="builder">Distributed application builder</param>
/// <param name="name">Name of parameter resource</param>
/// <param name="secret">Flag indicating whether the parameter should be regarded as secret.</param>
/// <returns>The created <see cref="ParameterResource"/>.</returns>
public static ParameterResource CreateParameter(IDistributedApplicationBuilder builder, string name, bool secret)

/// <remarks>
/// Format: <c>mongodb://[user:password@]{host}:{port}/{database}[?authSource=admin&amp;authMechanism=SCRAM-SHA-256]</c>. The credential and query segments are included only when a password is configured.
/// </remarks>
public ReferenceExpression UriExpression => Parent.BuildConnectionString(DatabaseName);
Copy link
Member

Choose a reason for hiding this comment

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

How is this different than ConnectionStringExpression? Looks like they both do the same implementation.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought this could be useful if we need to change one or the other to have a separate property. Or do you mean UriExpression => ConnectionStringExpression ?

Copy link
Member

Choose a reason for hiding this comment

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

I guess I was surprised that there are 2 public properties that return the same information. If it is a common pattern to have both, then that's fine.

Or do you mean UriExpression => ConnectionStringExpression ?

That would be a nice to have change to show that they are the same.

IEnumerable<KeyValuePair<string, ReferenceExpression>> IResourceWithConnectionString.GetConnectionProperties() =>
Parent.CombineProperties([
new("Database", ReferenceExpression.Create($"{DatabaseName}")),
new("JdbcConnectionString", JdbcConnectionString),
Copy link
Member

Choose a reason for hiding this comment

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

If both the Parent and the Child have the same JdbcConnectionString, the child's value wins, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, there is a unit test to ensure that. It also happens for Uri in other resources.

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

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

Let's get this in so we can play with it and we can tweak as we go.

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

Lots of follow up expected after we merge this.

@sebastienros sebastienros merged commit 62155ab into main Oct 14, 2025
595 of 597 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 13.0 milestone Oct 14, 2025
@sebastienros sebastienros deleted the sebros/properties branch October 16, 2025 00:37
@github-actions github-actions bot locked and limited conversation to collaborators Nov 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants