-
Notifications
You must be signed in to change notification settings - Fork 719
Refactor user secrets to DI-based factory with thread-safe synchronization #12692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: davidfowl <[email protected]>
Co-authored-by: davidfowl <[email protected]>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12692Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12692" |
| <Compile Include="$(SharedDir)LoggingHelpers.cs" Link="Utils\LoggingHelpers.cs" /> | ||
| <Compile Include="$(SharedDir)StringUtils.cs" Link="Utils\StringUtils.cs" /> | ||
| <Compile Include="$(SharedDir)SchemaUtils.cs" Link="Utils\SchemaUtils.cs" /> | ||
| <Compile Include="$(SharedDir)SecretsStore.cs" Link="Utils\SecretsStore.cs" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the file be deleted now? This appears to be the only project that uses it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot delete this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in commit 81d3417. The file has been deleted as it's no longer used after the refactoring.
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="UserSecretsParameterDefault"/> class using the default factory. | ||
| /// </summary> | ||
| public UserSecretsParameterDefault(Assembly appHostAssembly, string applicationName, string parameterName, ParameterDefault parameterDefault) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot write tests that use this overload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The constructor taking a factory is used only by tests to enable test isolation. Tests create their own UserSecretsManagerFactory instances to avoid cross-contamination between test runs. Production code uses the parameterless constructor which delegates to the factory-based constructor with UserSecretsManagerFactory.Instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added 3 tests in commit 50647b4 that specifically use the constructor overload with factory parameter:
UserSecretsParameterDefault_WithCustomFactory_UsesProvidedFactory- Verifies the custom factory is usedUserSecretsParameterDefault_WithCustomFactory_IsolatesFromGlobalInstance- Tests isolation between custom factory and singleton instanceUserSecretsParameterDefault_WithCustomFactory_ConcurrentAccess- Validates thread safety with custom factory under concurrent access
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
![]()
There was a problem hiding this 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 refactors user secrets management in Aspire by introducing a new abstraction layer (IUserSecretsManager) and factory pattern (UserSecretsManagerFactory) to replace direct usage of SecretsStore from Microsoft.Extensions.SecretManager.Tools.Internal. The main goals are:
- Thread-safe concurrent writes: Uses a
SemaphoreSlimto synchronize access to user secrets files, preventing race conditions when multiple resources (like SQL Server and RabbitMQ) generate passwords concurrently - Centralized management: Factory pattern ensures singleton instances per secrets file path, avoiding duplicate managers
- Improved testability: New abstraction allows for easier testing and mocking
- Code organization: Extracted JSON flattening logic into a separate
JsonFlattenerutility class
Reviewed Changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/Aspire.Hosting/UserSecrets/IUserSecretsManager.cs |
New interface defining the contract for user secrets management |
src/Aspire.Hosting/UserSecrets/UserSecretsManagerFactory.cs |
Factory for creating and caching thread-safe user secrets manager instances |
src/Aspire.Hosting/UserSecrets/NoopUserSecretsManager.cs |
No-op implementation for scenarios without user secrets configured |
src/Aspire.Hosting/Pipelines/Internal/JsonFlattener.cs |
Extracted JSON flattening utility methods |
src/Aspire.Hosting/Pipelines/Internal/UserSecretsDeploymentStateManager.cs |
Updated to use IUserSecretsManager instead of direct file operations |
src/Aspire.Hosting/Pipelines/Internal/FileDeploymentStateManager.cs |
Updated to use JsonFlattener utility |
src/Aspire.Hosting/Pipelines/Internal/DeploymentStateManagerBase.cs |
Removed JSON flattening methods (moved to JsonFlattener) |
src/Aspire.Hosting/VersionChecking/VersionCheckService.cs |
Updated to use IUserSecretsManager instead of SecretsStore |
src/Aspire.Hosting/DistributedApplicationBuilder.cs |
Registers IUserSecretsManager in DI container and uses it for API key generation |
src/Aspire.Hosting/ApplicationModel/UserSecretsParameterDefault.cs |
Updated to use factory pattern for user secrets management |
src/Aspire.Hosting/Aspire.Hosting.csproj |
Removed reference to shared SecretsStore.cs |
tests/Aspire.Hosting.Tests/UserSecretsParameterDefaultTests.cs |
Added comprehensive concurrency tests; updated helper methods |
tests/Aspire.Hosting.Tests/SecretsStoreTests.cs |
Updated to use new user secrets manager APIs |
tests/Aspire.Hosting.Tests/JsonFlattenerTests.cs |
Renamed from UserSecretsDeploymentStateManagerTests; updated to test JsonFlattener |
tests/Aspire.Hosting.Tests/VersionChecking/VersionCheckServiceTests.cs |
Updated test helper to accept IUserSecretsManager parameter |
| namespace Aspire.Hosting.UserSecrets; | ||
|
|
||
| /// <summary> | ||
| /// Manages user secrets for an application, providing thread-safe read and write operations. |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The summary states "providing thread-safe read and write operations" but the interface itself doesn't guarantee thread-safety - this is an implementation detail of UserSecretsManager. Consider rephrasing to "Interface for managing user secrets with support for read and write operations" to avoid making guarantees about implementation details in the interface documentation.
| /// Manages user secrets for an application, providing thread-safe read and write operations. | |
| /// Defines an interface for managing user secrets with support for read and write operations. |
| /// Factory for creating and caching IUserSecretsManager instances. | ||
| /// Uses a lock to ensure thread-safe creation and a dictionary to cache instances by normalized file path. | ||
| /// </summary> |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The summary mentions implementation details (lock, dictionary) that should be in <remarks> instead. The <summary> should be brief and focus on the purpose. Suggest: "Factory for creating and caching IUserSecretsManager instances." with additional details moved to a <remarks> section.
| /// Factory for creating and caching IUserSecretsManager instances. | |
| /// Uses a lock to ensure thread-safe creation and a dictionary to cache instances by normalized file path. | |
| /// </summary> | |
| /// Factory for creating and caching <see cref="IUserSecretsManager"/> instances. | |
| /// </summary> | |
| /// <remarks> | |
| /// Uses a lock to ensure thread-safe creation and a dictionary to cache instances by normalized file path. | |
| /// </remarks> |
| /// A no-op implementation of <see cref="IUserSecretsManager"/> used when | ||
| /// user secrets are not configured for a project. |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The summary should be a single sentence or short paragraph. The text should flow as one sentence without line break. Suggest: "A no-op implementation of used when user secrets are not configured for a project."
| /// A no-op implementation of <see cref="IUserSecretsManager"/> used when | |
| /// user secrets are not configured for a project. | |
| /// A no-op implementation of <see cref="IUserSecretsManager"/> used when user secrets are not configured for a project. |
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="UserSecretsDeploymentStateManager"/> class. | ||
| /// </summary> | ||
| /// <param name="logger">The logger.</param> | ||
| /// <param name="userSecretsManager">User secrets manager for managing secrets.</param> |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per Aspire coding guidelines, constructors for internal classes should have minimal documentation. This verbose documentation with full summary and parameter descriptions is unnecessary for an internal class. Simplify to just a brief <summary> tag.
| /// <summary> | ||
| /// Initializes a new instance of the <see cref="UserSecretsParameterDefault"/> class using the default factory. | ||
| /// </summary> |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per Aspire coding guidelines, internal classes should have minimal documentation. This verbose constructor documentation is unnecessary for an internal class constructor. A brief <summary> would suffice, or it could be omitted entirely.
Co-authored-by: davidfowl <[email protected]>
…rameter Co-authored-by: davidfowl <[email protected]>
|
/backport to release/13.0 |
|
Started backporting to release/13.0: https://github.com/dotnet/aspire/actions/runs/19091023335 |
|
@davidfowl backporting to "release/13.0" failed, the patch most likely resulted in conflicts: $ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch
Creating an empty commit: Initial plan
Applying: Implement user secrets refactoring with DI-based factory pattern
.git/rebase-apply/patch:59: trailing whitespace.
.git/rebase-apply/patch:113: trailing whitespace.
.git/rebase-apply/patch:1114: trailing whitespace.
.git/rebase-apply/patch:1125: trailing whitespace.
.git/rebase-apply/patch:1161: new blank line at EOF.
+
warning: 5 lines add whitespace errors.
Using index info to reconstruct a base tree...
M src/Aspire.Hosting/DistributedApplicationBuilder.cs
A src/Aspire.Hosting/Pipelines/Internal/DeploymentStateManagerBase.cs
A src/Aspire.Hosting/Pipelines/Internal/FileDeploymentStateManager.cs
A src/Aspire.Hosting/Pipelines/Internal/UserSecretsDeploymentStateManager.cs
A tests/Aspire.Hosting.Tests/Publishing/DefaultUserSecretsManagerTests.cs
Falling back to patching base and 3-way merge...
Auto-merging src/Aspire.Hosting/DistributedApplicationBuilder.cs
Auto-merging src/Aspire.Hosting/Publishing/Internal/DeploymentStateManagerBase.cs
Auto-merging src/Aspire.Hosting/Publishing/Internal/FileDeploymentStateManager.cs
Auto-merging src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs
CONFLICT (content): Merge conflict in src/Aspire.Hosting/Publishing/Internal/UserSecretsDeploymentStateManager.cs
Auto-merging tests/Aspire.Hosting.Tests/Publishing/DefaultUserSecretsManagerTests.cs
CONFLICT (rename/rename): tests/Aspire.Hosting.Tests/Publishing/DefaultUserSecretsManagerTests.cs renamed to tests/Aspire.Hosting.Azure.Tests/DefaultUserSecretsManagerTests.cs in HEAD and to tests/Aspire.Hosting.Tests/JsonFlattenerTests.cs in Implement user secrets refactoring with DI-based factory pattern.
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0002 Implement user secrets refactoring with DI-based factory pattern
Error: The process '/usr/bin/git' failed with exit code 128Please backport manually! |
Apply changes from PR #12607 to main
This PR applies the user secrets refactoring from PR #12607 to the main branch. The refactoring moves from static methods to a dependency injection-based factory pattern with proper thread-safe synchronization.
Changes implemented:
IUserSecretsManagerinterfaceUserSecretsManagerFactorywith lock-based cachingNoopUserSecretsManagerfor null object patternJsonFlattenerutility classDistributedApplicationBuilderto register user secrets managerUserSecretsParameterDefaultto use factoryUserSecretsDeploymentStateManagerto use DIVersionCheckServiceto inject managerDeploymentStateManagerBasetoJsonFlattenerFileDeploymentStateManagerto useJsonFlattenerSecretsStore.csfromAspire.Hosting.csprojsrc/Shared/SecretsStore.cs(no longer used)Architecture:
Test Coverage:
The factory-based constructor overload in
UserSecretsParameterDefaultis tested through:UserSecretsParameterDefault_WithCustomFactory_UsesProvidedFactory- Verifies custom factory usageUserSecretsParameterDefault_WithCustomFactory_IsolatesFromGlobalInstance- Tests isolation between custom and singleton instancesUserSecretsParameterDefault_WithCustomFactory_ConcurrentAccess- Validates thread safety with custom factoryValidation:
Aspire.Hostingproject builds successfullyAspire.Hosting.Testsproject builds successfullyThe implementation successfully replicates the changes from PR #12607, providing a thread-safe, DI-based approach to managing user secrets with proper synchronization to prevent concurrent access issues.
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.