From ce47984c2390f05f22b110aca5ada58ba417278e Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 24 Apr 2025 14:13:42 +0800 Subject: [PATCH 01/17] add app configuration provider as an aspire component --- Aspire.sln | 31 +++- Directory.Packages.props | 1 + ...pire.Hosting.Azure.AppConfiguration.csproj | 1 + .../Aspire.Azure.AppConfiguration.csproj | 29 ++++ .../AspireAppConfigurationExtensions.cs | 115 ++++++++++++ .../AssemblyInfo.cs | 10 ++ .../AzureAppConfigurationSettings.cs | 41 +++++ .../ConfigurationSchema.json | 40 +++++ .../Aspire.Azure.AppConfiguration/README.md | 164 ++++++++++++++++++ src/Components/Aspire_Components_Progress.md | 1 + .../AppConfiguratinPublicApiTests.cs | 60 +++++++ ...Aspire.Azure.AppConfiguration.Tests.csproj | 21 +++ .../AspireAppConfigurationExtensionsTest.cs | 153 ++++++++++++++++ .../ConfigurationTests.cs | 17 ++ .../ConformanceTests.cs | 123 +++++++++++++ .../ConformanceTests.cs | 12 ++ 16 files changed, 818 insertions(+), 1 deletion(-) create mode 100644 src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj create mode 100644 src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs create mode 100644 src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs create mode 100644 src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs create mode 100644 src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json create mode 100644 src/Components/Aspire.Azure.AppConfiguration/README.md create mode 100644 tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs create mode 100644 tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj create mode 100644 tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs create mode 100644 tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs create mode 100644 tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs diff --git a/Aspire.sln b/Aspire.sln index fe5408bd274..64bc1b3899a 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -675,6 +674,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.Npgsql.EntityF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Components.Common.Tests", "tests\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj", "{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration", "src\Components\Aspire.Azure.AppConfiguration\Aspire.Azure.AppConfiguration.csproj", "{C33CE874-27B7-4194-A2E7-D0CD950997CC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration.Tests", "tests\Aspire.Azure.AppConfiguration.Tests\Aspire.Azure.AppConfiguration.Tests.csproj", "{FE972BF3-F448-4CF0-8544-0056B26BF006}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -3961,6 +3964,30 @@ Global {30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}.Release|x64.Build.0 = Release|Any CPU {30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}.Release|x86.ActiveCfg = Release|Any CPU {30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}.Release|x86.Build.0 = Release|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Debug|x64.Build.0 = Debug|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Debug|x86.Build.0 = Debug|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Release|Any CPU.Build.0 = Release|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Release|x64.ActiveCfg = Release|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Release|x64.Build.0 = Release|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Release|x86.ActiveCfg = Release|Any CPU + {C33CE874-27B7-4194-A2E7-D0CD950997CC}.Release|x86.Build.0 = Release|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Debug|x64.ActiveCfg = Debug|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Debug|x64.Build.0 = Debug|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Debug|x86.ActiveCfg = Debug|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Debug|x86.Build.0 = Debug|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Release|Any CPU.Build.0 = Release|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Release|x64.ActiveCfg = Release|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Release|x64.Build.0 = Release|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Release|x86.ActiveCfg = Release|Any CPU + {FE972BF3-F448-4CF0-8544-0056B26BF006}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -4285,6 +4312,8 @@ Global {192747A2-9338-DECF-5C8C-28EB8E13829B} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} {8FCA0CFA-7823-6A2F-342A-107A994915B0} = {C424395C-1235-41A4-BF55-07880A04368C} {30950CEB-2232-F9FC-04FF-ADDCB8AC30A7} = {C424395C-1235-41A4-BF55-07880A04368C} + {C33CE874-27B7-4194-A2E7-D0CD950997CC} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2} + {FE972BF3-F448-4CF0-8544-0056B26BF006} = {C424395C-1235-41A4-BF55-07880A04368C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {47DCFECF-5631-4BDE-A1EC-BE41E90F60C4} diff --git a/Directory.Packages.props b/Directory.Packages.props index b3825726499..feb0bc657ed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,6 +23,7 @@ + diff --git a/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj b/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj index c95ee5073e1..e99839bf3ef 100644 --- a/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj +++ b/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj b/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj new file mode 100644 index 00000000000..4be18be6eb2 --- /dev/null +++ b/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj @@ -0,0 +1,29 @@ + + + + $(DefaultTargetFramework) + true + $(ComponentAzurePackageTags) configuration appconfiguration + A client for Azure App Configuration that integrates with Aspire. + $(SharedDir)AzureAppConfig_256x.png + $(NoWarn);SYSLIB1100;SYSLIB1101 + + false + + + + + + + + + + + + + + + + + + diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs new file mode 100644 index 00000000000..56c0b15823c --- /dev/null +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Azure.AppConfiguration; +using Azure.Identity; +using Microsoft.Extensions.Configuration.AzureAppConfiguration; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Aspire.Azure.Common; + +namespace Microsoft.Extensions.Hosting; + +/// +/// Provides extension methods for registering and configuring Azure App Configuration in a .NET Aspire application. +/// +public static class AspireAppConfigurationExtensions +{ + internal const string DefaultConfigSectionName = "Aspire:Azure:AppConfiguration"; + + /// + /// Adds the Azure App Configuration to be configuration in the . + /// + /// The to read config from and add services to. + /// A name used to retrieve the connection string from the ConnectionStrings configuration section. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + /// An optional method that can be used for customizing the . + /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration. + /// Reads the configuration from "Aspire:Azure:Data:AppConfiguration" section. + /// Thrown when mandatory is not provided. + public static void AddAzureAppConfiguration( + this IHostApplicationBuilder builder, + string connectionName, + Action? configureSettings = null, + Action? configureOptions = null, + bool optional = false) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentException.ThrowIfNullOrEmpty(connectionName); + + AzureAppConfigurationSettings settings = GetSettings(builder.Configuration, connectionName, configureSettings); + + builder.Configuration.AddAzureAppConfiguration( + options => + { + options.Connect(settings.Endpoint, settings.Credential ?? new DefaultAzureCredential()); + configureOptions?.Invoke(options); + }, + optional); + + builder.Services.AddAzureAppConfiguration(); // register IConfigurationRefresherProvider service + + if (!settings.DisableTracing) + { + builder.Services.AddOpenTelemetry() + .WithTracing(traceBuilder => + traceBuilder.AddSource(["Microsoft.Extensions.Configuration.AzureAppConfiguration"])); + } + } + + /// + /// Adds the Azure App Configuration to be configuration values in the . + /// + /// The to add the secrets to. + /// A name used to retrieve the connection string from the ConnectionStrings configuration section. + /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. + /// An optional method that can be used for customizing the . + /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration. + /// Reads the configuration from "Aspire:Azure:Data:AppConfiguration" section. + /// Thrown when mandatory is not provided. + public static IConfigurationBuilder AddAzureAppConfiguration( + this IConfigurationManager configurationManager, + string connectionName, + Action? configureSettings = null, + Action? configureOptions = null, + bool optional = false) + { + ArgumentNullException.ThrowIfNull(configurationManager); + ArgumentException.ThrowIfNullOrEmpty(connectionName); + + AzureAppConfigurationSettings settings = GetSettings(configurationManager, connectionName, configureSettings); + + return configurationManager.AddAzureAppConfiguration( + options => + { + options.Connect(settings.Endpoint, settings.Credential ?? new DefaultAzureCredential()); + configureOptions?.Invoke(options); + }, + optional); + } + + private static AzureAppConfigurationSettings GetSettings( + IConfiguration configuration, + string connectionName, + Action? configureSettings) + { + IConfigurationSection configSection = configuration.GetSection(DefaultConfigSectionName); + + var settings = new AzureAppConfigurationSettings(); + configSection.Bind(settings); + + if (configuration.GetConnectionString(connectionName) is string connectionString) + { + ((IConnectionStringSettings)settings).ParseConnectionString(connectionString); + } + + configureSettings?.Invoke(settings); + + if (settings.Endpoint is null) + { + throw new InvalidOperationException($"Endpoint is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'Endpoint' key in the '{DefaultConfigSectionName}' configuration section."); + } + + return settings; + } +} diff --git a/src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs b/src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs new file mode 100644 index 00000000000..da7f647a848 --- /dev/null +++ b/src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire; +using Aspire.Azure.AppConfiguration; + +[assembly: ConfigurationSchema("Aspire:Azure:AppConfiguration", typeof(AzureAppConfigurationSettings))] + +[assembly: LoggingCategories( + "Microsoft.Extensions.Configuration.AzureAppConfiguration.Refresh")] diff --git a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs new file mode 100644 index 00000000000..5015bcd6fb5 --- /dev/null +++ b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Azure.Common; +using Azure.Core; + +namespace Aspire.Azure.AppConfiguration; + +/// +/// Provides the client configuration settings for connecting to Azure App Configuration. +/// +public sealed class AzureAppConfigurationSettings : IConnectionStringSettings +{ + /// + /// A to the App Config store on which the client operates. Appears as "Endpoint" in the Azure portal. + /// This is likely to be similar to "https://{store_name}.azconfig.io". + /// + public Uri? Endpoint { get; set; } + + /// + /// Gets or sets the credential used to authenticate to the Azure App Configuration. + /// + public TokenCredential? Credential { get; set; } + + /// + /// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not. + /// + /// + /// The default value is . + /// + public bool DisableTracing { get; set; } + + void IConnectionStringSettings.ParseConnectionString(string? connectionString) + { + if (!string.IsNullOrEmpty(connectionString) && + Uri.TryCreate(connectionString, UriKind.Absolute, out var uri)) + { + Endpoint = uri; + } + } +} diff --git a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json new file mode 100644 index 00000000000..4224107fdd2 --- /dev/null +++ b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json @@ -0,0 +1,40 @@ +{ + "definitions": { + "logLevel": { + "properties": { + "Microsoft.Extensions.Configuration.AzureAppConfiguration.Refresh": { + "$ref": "#/definitions/logLevelThreshold" + } + } + } + }, + "type": "object", + "properties": { + "Aspire": { + "type": "object", + "properties": { + "Azure": { + "type": "object", + "properties": { + "AppConfiguration": { + "type": "object", + "properties": { + "DisableTracing": { + "type": "boolean", + "description": "Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not.", + "default": false + }, + "Endpoint": { + "type": "string", + "format": "uri", + "description": "A 'System.Uri' to the App Config store on which the client operates. Appears as \"Endpoint\" in the Azure portal. This is likely to be similar to \"https://{store_name}.azconfig.io\"." + } + }, + "description": "Provides the client configuration settings for connecting to Azure App Configuration." + } + } + } + } + } + } +} diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Azure.AppConfiguration/README.md new file mode 100644 index 00000000000..82ca8b2d491 --- /dev/null +++ b/src/Components/Aspire.Azure.AppConfiguration/README.md @@ -0,0 +1,164 @@ +# Aspire.Azure.AppConfiguration + +Retrieves configuration settings from Azure App Configuration to use in your application. Registers Azure App Configuration service as a configuration source. Enables corresponding logging and telemetry. + +## Getting started + +### Prerequisites + +- Azure subscription - [create one for free](https://azure.microsoft.com/free/) +- Azure App Configuration - [create one](https://learn.microsoft.com/azure/azure-app-configuration/quickstart-azure-app-configuration-create). + +### Install the package + +Install the .NET Aspire Azure App Configuration library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package Aspire.Azure.AppConfiguration +``` + +## Usage examples + +### Add App Configuration to configuration + +In the _Program.cs_ file of your project, call the `builder.Configuration.AddAzureAppConfiguration` extension method to add key-values from Azure App Configuration to the application's Configuration. The method takes a connection name parameter. + +```csharp +builder.AddAzureAppConfiguration("appConfig"); +``` + +You can then retrieve a key-value through normal `IConfiguration` APIS. For example, to retrieve a key-value from a Web API controller: + +```csharp +public MyController(IConfiguration configuration) +{ + string someValue = configuration["someKey"]; +} +``` + +#### Use feature flags + +To use feature flags, install the Feature Management library: + +```dotnetcli +dotnet add package Microsoft.FeatureManagement +``` + +App Configuration will not load feature flags by default. To load feature flags, you can pass the `Action configureOptions` delegate when calling `builder.AddAzureAppConfiguration`. + +```csharp +builder.AddAzureAppConfiguration( + "appConfig", + configureOptions: options => options.UseFeatureFlags()); + +// Register feature management services +builder.Services.AddFeatureManagement(); +``` + +You can then use `IVariantFeatureManager` to evaluate feature flags in your application: + +```csharp +private readonly IVariantFeatureManager _featureManager; + +public MyController(IVariantFeatureManager featureManager) +{ + _featureManager = featureManager; +} + +[HttpGet] +public async Task Get() +{ + if (await _featureManager.IsEnabledAsync("NewFeature")) + { + return Ok("New feature is enabled!"); + } + + return Ok("Using standard implementation."); +} +``` + +For information about using the Feature Management library, please go to the [documentation](https://learn.microsoft.com/azure/azure-app-configuration/feature-management-dotnet-reference). + +## Configuration + +The .NET Aspire Azure App Configuration library provides multiple options to configure the Azure App Configuration connection based on the requirements and conventions of your project. Note that the App Config `Endpoint` is required to be supplied. + +### Use a connection string + +When using a connection string from the `ConnectionStrings` configuration section, you can provide the name of the connection string when calling `builder.AddAzureAppConfiguration()`: + +```csharp +builder.AddAzureAppConfiguration("appConfigConnectionName"); +``` + +And then the App Config endpoint will be retrieved from the `ConnectionStrings` configuration section. The App Config store URI which works with the `AzureAppConfigurationSettings.Credential` property to establish a connection. If no credential is configured, the [DefaultAzureCredential](https://learn.microsoft.com/dotnet/api/azure.identity.defaultazurecredential) is used. + +```json +{ + "ConnectionStrings": { + "appConfigConnectionName": "https://{store_name}.azconfig.io" + } +} +``` + +### Use configuration providers + +The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `AzureAppConfigurationSettings` from configuration by using the `Aspire:Azure:AppConfiguration` key. Example `appsettings.json` that configures some of the options: + +```json +{ + "Aspire": { + "Azure": { + "Data": { + "AppConfiguration": { + "DisableTracing": false + } + } + } + } +} +``` + +### Use inline delegates + +You can also pass the `Action configureSettings` delegate to set up some or all the options inline, for example to disable tracing from code: + +```csharp +builder.AddAzureAppConfiguration("appConfig", configureSettings: settings => settings.DisableTracing = true); +``` + +## AppHost extensions + +In your AppHost project, install the Aspire Azure App Configuration Hosting library with [NuGet](https://www.nuget.org): + +```dotnetcli +dotnet add package Aspire.Hosting.Azure.AppConfiguration +``` + +Then, in the _Program.cs_ file of `AppHost`, add a App Configuration connection and consume the connection using the following methods: + +```csharp +// Service registration +var appConfig = builder.ExecutionContext.IsPublishMode + ? builder.AddAzureAppConfiguration("appConfig") + : builder.AddConnectionString("appConfig"); + +// Service consumption +var myService = builder.AddProject() + .WithReference(appConfig); +``` + +The `AddAzureAppConfiguration` method adds an Azure App Configuration resource to the builder. Or `AddConnectionString` can be used to read connection information from the AppHost's configuration under the `ConnectionStrings:appConfig` config key. The `WithReference` method passes that connection information into a connection string named `appConfig` in the `MyService` project. In the _Program.cs_ file of `MyService`, the connection can be consumed using: + +```csharp +builder.AddAzureAppConfiguration("appConfig"); +``` + +## Additional documentation + +* https://learn.microsoft.com/azure/azure-app-configuration/ +* https://github.com/dotnet/aspire/tree/main/src/Components/README.md + +## Feedback & contributing + +https://github.com/dotnet/aspire diff --git a/src/Components/Aspire_Components_Progress.md b/src/Components/Aspire_Components_Progress.md index dcf29bfb7af..a35640f5070 100644 --- a/src/Components/Aspire_Components_Progress.md +++ b/src/Components/Aspire_Components_Progress.md @@ -12,6 +12,7 @@ As part of the .NET Aspire November preview, we want to include a set of .NET As | Microsoft.EntityFrameworkCore.SqlServer | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | MongoDB.Driver | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.AI.OpenAI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | +| Azure.Data.AppConfiguration | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | | Azure.Data.Tables | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.Messaging.EventHubs | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | | Azure.Messaging.WebPubSub | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs new file mode 100644 index 00000000000..7cfbd69079a --- /dev/null +++ b/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Aspire.Azure.AppConfiguration.Tests; + +public class DataAppConfigurationPublicApiTests +{ + [Fact] + public void AddAzureAppConfigurationShouldThrowWhenBuilderIsNull() + { + IHostApplicationBuilder builder = null!; + const string connectionName = "appConfig"; + + var action = () => builder.AddAzureAppConfiguration(connectionName); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(builder), exception.ParamName); + } + + [Fact] + public void AddAzureAppConfigurationShouldThrowWhenConfigurationManagerIsNull() + { + IConfigurationManager configurationManager = null!; + const string connectionName = "appConfig"; + + var action = () => configurationManager.AddAzureAppConfiguration(connectionName); + + var exception = Assert.Throws(action); + Assert.Equal(nameof(configurationManager), exception.ParamName); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void AddAzureAppConfigurationShouldThrowWhenConnectionNameIsNullOrEmpty(bool isNull) + { + var builder = Host.CreateEmptyApplicationBuilder(null); + var connectionName = isNull ? null! : string.Empty; + + var action = () => builder.AddAzureAppConfiguration(connectionName); + + var exception = isNull + ? Assert.Throws(action) + : Assert.Throws(action); + Assert.Equal(nameof(connectionName), exception.ParamName); + + var configurationManager = new ConfigurationManager(); + + action = () => configurationManager.AddAzureAppConfiguration(connectionName); + + exception = isNull + ? Assert.Throws(action) + : Assert.Throws(action); + Assert.Equal(nameof(connectionName), exception.ParamName); + } +} diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj b/tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj new file mode 100644 index 00000000000..ca93ee8de90 --- /dev/null +++ b/tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj @@ -0,0 +1,21 @@ + + + + $(DefaultTargetFramework) + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs b/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs new file mode 100644 index 00000000000..60d84d25e99 --- /dev/null +++ b/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs @@ -0,0 +1,153 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Azure.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using System.Text; +using Xunit; + +namespace Aspire.Azure.AppConfiguration.Tests; + +public class AspireAppConfigurationExtensionsTest +{ + [Fact] + public void AppConfigEndpointCanBeSetInCode() + { + var endpoint = new Uri(ConformanceTests.Endpoint); + var mockTransport = new MockTransport(CreateResponse("""{}""")); + + var builder = Host.CreateEmptyApplicationBuilder(null); + builder.Configuration.AddInMemoryCollection([ + new KeyValuePair("ConnectionStrings:appConfig", "https://unused.azconfig.io/") + ]); + + builder.AddAzureAppConfiguration( + "appConfig", + settings => { + settings.Endpoint = endpoint; + settings.Credential = new EmptyTokenCredential(); + }, + options => options.ConfigureClientOptions(clientOptions => clientOptions.Transport = mockTransport)); + + Assert.NotEmpty(mockTransport.Requests); + var request = mockTransport.Requests[0]; + Assert.StartsWith(endpoint.ToString(), request.Uri.ToString()); + } + + [Fact] + public void ConnectionNameWinsOverConfiguration() + { + var endpoint = new Uri(ConformanceTests.Endpoint); + var mockTransport = new MockTransport(CreateResponse("""{}""")); + var builder = Host.CreateEmptyApplicationBuilder(null); + builder.Configuration.AddInMemoryCollection([ + new KeyValuePair(ConformanceTests.CreateConfigKey("Aspire:Azure:Data:AppConfiguration", null, "Endpoint"), "https://unused.azconfig.io/"), + new KeyValuePair("ConnectionStrings:appConfig", ConformanceTests.Endpoint) + ]); + + builder.AddAzureAppConfiguration( + "appConfig", + settings => + { + settings.Endpoint = endpoint; + settings.Credential = new EmptyTokenCredential(); + }, + options => options.ConfigureClientOptions(clientOptions => clientOptions.Transport = mockTransport)); + + Assert.NotEmpty(mockTransport.Requests); + var request = mockTransport.Requests[0]; + Assert.StartsWith(endpoint.ToString(), request.Uri.ToString()); + } + + [Fact] + public void AddsAppConfigurationToApplication() + { + var endpoint = new Uri("https://aspiretests.azconfig.io/"); + var mockTransport = new MockTransport(CreateResponse(""" + { + "items": [ + { + "key": "test-key-1", + "value": "test-value-1" + }, + { + "key": "test-key-2", + "value": "test-value-2" + } + ] + } + """)); + + var builder = Host.CreateEmptyApplicationBuilder(null); + builder.AddAzureAppConfiguration( + "appConfig", + settings => + { + settings.Endpoint = endpoint; + settings.Credential = new EmptyTokenCredential(); + }, + options => options.ConfigureClientOptions(clientOptions => clientOptions.Transport = mockTransport)); + + Assert.Equal("test-value-1", builder.Configuration["test-key-1"]); + Assert.Equal("test-value-2", builder.Configuration["test-key-2"]); + } + + [Fact] + public void AddsAppConfigurationToConfiguration() + { + var endpoint = new Uri("https://aspiretests.azconfig.io/"); + var mockTransport = new MockTransport(CreateResponse(""" + { + "items": [ + { + "key": "test-key-1", + "value": "test-value-1" + }, + { + "key": "test-key-2", + "value": "test-value-2" + } + ] + } + """)); + + var configurationManager = new ConfigurationManager(); + configurationManager.AddAzureAppConfiguration( + "appConfig", + settings => + { + settings.Endpoint = endpoint; + settings.Credential = new EmptyTokenCredential(); + }, + options => options.ConfigureClientOptions(clientOptions => clientOptions.Transport = mockTransport)); + + Assert.Equal("test-value-1", configurationManager["test-key-1"]); + Assert.Equal("test-value-2", configurationManager["test-key-2"]); + } + + private static MockResponse CreateResponse(string content) + { + var buffer = Encoding.UTF8.GetBytes(content); + var response = new MockResponse(200) + { + ClientRequestId = Guid.NewGuid().ToString(), + ContentStream = new MemoryStream(buffer), + }; + + return response; + } + + internal sealed class EmptyTokenCredential : TokenCredential + { + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new AccessToken(string.Empty, DateTimeOffset.MaxValue); + } + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new ValueTask(new AccessToken(string.Empty, DateTimeOffset.MaxValue)); + } + } +} diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs new file mode 100644 index 00000000000..0826085160a --- /dev/null +++ b/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace Aspire.Azure.AppConfiguration.Tests; + +public class ConfigurationTests +{ + [Fact] + public void EndpointUriIsNullByDefault() + => Assert.Null(new AzureAppConfigurationSettings().Endpoint); + + [Fact] + public void TracingIsEnabledByDefault() + => Assert.False(new AzureAppConfigurationSettings().DisableTracing); +} diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs new file mode 100644 index 00000000000..2a1c3f7a72f --- /dev/null +++ b/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +//using System.Text; +using Aspire.Components.ConformanceTests; +using Azure.Core; +using Microsoft.DotNet.RemoteExecutor; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.AzureAppConfiguration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Reflection; +using Xunit; + +namespace Aspire.Azure.AppConfiguration.Tests; + +public class ConformanceTests : ConformanceTests +{ + public const string Endpoint = "https://aspiretests.azconfig.io/"; + + protected override ServiceLifetime ServiceLifetime => ServiceLifetime.Singleton; + + protected override string ActivitySourceName => "Microsoft.Extensions.Configuration.AzureAppConfiguration"; + + protected override string[] RequiredLogCategories => new string[] { "Microsoft.Extensions.Configuration.AzureAppConfiguration.Refresh" }; + + protected override bool SupportsKeyedRegistrations => false; + + protected override bool IsComponentBuiltBeforeHost => true; + + protected override string ValidJsonConfig => """ + { + "Aspire": { + "Azure": { + "Data": { + "AppConfiguration": { + "Endpoint": "http://YOUR_URI", + "DisableHealthChecks": true, + "DisableTracing": false + } + } + } + } + } + """; + + protected override void PopulateConfiguration(ConfigurationManager configuration, string? key = null) + => configuration.AddInMemoryCollection(new KeyValuePair[] + { + new(CreateConfigKey("Aspire:Azure:AppConfiguration", null, "Endpoint"), Endpoint) + }); + + protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null) + { + builder.AddAzureAppConfiguration( + "appconfig", + settings => + { + configure?.Invoke(settings); + settings.Credential = new EmptyTokenCredential(); + }, + options => + { + // AzureAppConfigurationOptions.MinBackoffDuration is internal, use reflection to set it to 1 second to facilitate testing. + var minBackoffDurationProperty = options.GetType().GetProperty("MinBackoffDuration", BindingFlags.Instance | BindingFlags.NonPublic); + minBackoffDurationProperty?.SetValue(options, TimeSpan.FromSeconds(1)); + + options.ConfigureRefresh(refreshOptions => + { + refreshOptions.Register("sentinel") + .SetRefreshInterval(TimeSpan.FromSeconds(1)); + }); + options.ConfigureStartupOptions(startupOptions => + { + startupOptions.Timeout = TimeSpan.FromSeconds(1); + }); + options.ConfigureClientOptions(clientOptions => clientOptions.Retry.MaxRetries = 0); + }, + optional: true); + } + + protected override (string json, string error)[] InvalidJsonToErrorMessage => new[] + { + ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\""), + ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "http://YOUR_URI", "DisableTracing": "true"}}}}""", "Value is \"string\" but should be \"boolean\""), + }; + + protected override void SetHealthCheck(AzureAppConfigurationSettings options, bool enabled) + // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/644 + => throw new NotImplementedException(); + + protected override void SetMetrics(AzureAppConfigurationSettings options, bool enabled) + => throw new NotImplementedException(); + + protected override void SetTracing(AzureAppConfigurationSettings options, bool enabled) + // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 + // Will be supported in the next 8.2.0 release + => throw new NotImplementedException(); + + protected override void TriggerActivity(IConfigurationRefresherProvider service) + { + Thread.Sleep(1000); + service.Refreshers.First().RefreshAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + } + + [Fact] + public void TracingEnablesTheRightActivitySource() + // WIP: Waiting for App Configuration Provider 8.2.0 release + => RemoteExecutor.Invoke(() => /*ActivitySourceTest(key: null)*/ null).Dispose(); + + internal sealed class EmptyTokenCredential : TokenCredential + { + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new AccessToken(string.Empty, DateTimeOffset.MaxValue); + } + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new ValueTask(new AccessToken(string.Empty, DateTimeOffset.MaxValue)); + } + } +} diff --git a/tests/Aspire.Components.Common.Tests/ConformanceTests.cs b/tests/Aspire.Components.Common.Tests/ConformanceTests.cs index c6f3523a1d9..5b7f55cc12e 100644 --- a/tests/Aspire.Components.Common.Tests/ConformanceTests.cs +++ b/tests/Aspire.Components.Common.Tests/ConformanceTests.cs @@ -45,6 +45,8 @@ public abstract class ConformanceTests protected virtual bool SupportsKeyedRegistrations => false; + protected virtual bool IsComponentBuiltBeforeHost => false; + protected bool MetricsAreSupported => CheckIfImplemented(SetMetrics); // every Component has to support health checks, this property is a temporary workaround @@ -366,6 +368,8 @@ public void ConfigurationSchemaInvalidJsonConfigTest() [InlineData(false)] public void ConnectionInformationIsDelayValidated(bool useKey) { + SkipIfComponentIsBuiltBeforeHost(); + SetupConnectionInformationIsDelayValidated(); var builder = Host.CreateEmptyApplicationBuilder(null); @@ -546,6 +550,14 @@ protected void SkipIfNamedConfigNotSupported() public static string CreateConfigKey(string prefix, string? key, string suffix) => string.IsNullOrEmpty(key) ? $"{prefix}:{suffix}" : $"{prefix}:{key}:{suffix}"; + protected void SkipIfComponentIsBuiltBeforeHost() + { + if (IsComponentBuiltBeforeHost) + { + Assert.Skip("Component is built before host."); + } + } + protected HostApplicationBuilder CreateHostBuilder(HostApplicationBuilderSettings? hostSettings = null, string? key = null) { HostApplicationBuilder builder = Host.CreateEmptyApplicationBuilder(hostSettings); From 13ac16793d66b9ea20f9bb4c750c76c58a684594 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 24 Apr 2025 14:19:59 +0800 Subject: [PATCH 02/17] update --- .../Aspire.Hosting.Azure.AppConfiguration.csproj | 1 - .../AspireAppConfigurationExtensions.cs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj b/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj index e99839bf3ef..c95ee5073e1 100644 --- a/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj +++ b/src/Aspire.Hosting.Azure.AppConfiguration/Aspire.Hosting.Azure.AppConfiguration.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs index 56c0b15823c..777376835da 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -51,6 +51,8 @@ public static void AddAzureAppConfiguration( if (!settings.DisableTracing) { + // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 + // Will be supported in the next 8.2.0 release builder.Services.AddOpenTelemetry() .WithTracing(traceBuilder => traceBuilder.AddSource(["Microsoft.Extensions.Configuration.AzureAppConfiguration"])); From 1f1a79fb7b12b9549ceac25a4c2f1f5e3c4b467d Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 24 Apr 2025 14:23:35 +0800 Subject: [PATCH 03/17] revert unintended change --- Aspire.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/Aspire.sln b/Aspire.sln index 64bc1b3899a..cc3ee60a960 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 From 5f7b39eb56d90f68146c1e965df260b83616f6ab Mon Sep 17 00:00:00 2001 From: zhiyuanliang Date: Fri, 25 Apr 2025 10:21:07 +0800 Subject: [PATCH 04/17] update comment typo --- .../AspireAppConfigurationExtensions.cs | 4 ++-- src/Components/Aspire.Azure.AppConfiguration/README.md | 2 +- .../AspireAppConfigurationExtensionsTest.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs index 777376835da..11ea240a224 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -25,7 +25,7 @@ public static class AspireAppConfigurationExtensions /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. /// An optional method that can be used for customizing the . /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration. - /// Reads the configuration from "Aspire:Azure:Data:AppConfiguration" section. + /// Reads the configuration from "Aspire:Azure:AppConfiguration" section. /// Thrown when mandatory is not provided. public static void AddAzureAppConfiguration( this IHostApplicationBuilder builder, @@ -67,7 +67,7 @@ public static void AddAzureAppConfiguration( /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. /// An optional method that can be used for customizing the . /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration. - /// Reads the configuration from "Aspire:Azure:Data:AppConfiguration" section. + /// Reads the configuration from "Aspire:Azure:AppConfiguration" section. /// Thrown when mandatory is not provided. public static IConfigurationBuilder AddAzureAppConfiguration( this IConfigurationManager configurationManager, diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Azure.AppConfiguration/README.md index 82ca8b2d491..0f3a133240b 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/README.md +++ b/src/Components/Aspire.Azure.AppConfiguration/README.md @@ -91,7 +91,7 @@ When using a connection string from the `ConnectionStrings` configuration sectio builder.AddAzureAppConfiguration("appConfigConnectionName"); ``` -And then the App Config endpoint will be retrieved from the `ConnectionStrings` configuration section. The App Config store URI which works with the `AzureAppConfigurationSettings.Credential` property to establish a connection. If no credential is configured, the [DefaultAzureCredential](https://learn.microsoft.com/dotnet/api/azure.identity.defaultazurecredential) is used. +And then the App Config endpoint will be retrieved from the `ConnectionStrings` configuration section. The App Config store URI works with the `AzureAppConfigurationSettings.Credential` property to establish a connection. If no credential is configured, the [DefaultAzureCredential](https://learn.microsoft.com/dotnet/api/azure.identity.defaultazurecredential) is used. ```json { diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs b/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs index 60d84d25e99..eda11a709f9 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs +++ b/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs @@ -42,7 +42,7 @@ public void ConnectionNameWinsOverConfiguration() var mockTransport = new MockTransport(CreateResponse("""{}""")); var builder = Host.CreateEmptyApplicationBuilder(null); builder.Configuration.AddInMemoryCollection([ - new KeyValuePair(ConformanceTests.CreateConfigKey("Aspire:Azure:Data:AppConfiguration", null, "Endpoint"), "https://unused.azconfig.io/"), + new KeyValuePair(ConformanceTests.CreateConfigKey("Aspire:Azure:AppConfiguration", null, "Endpoint"), "https://unused.azconfig.io/"), new KeyValuePair("ConnectionStrings:appConfig", ConformanceTests.Endpoint) ]); From 8eefef18ed5f8d58505973b110eff076e45b25bc Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Tue, 6 May 2025 13:32:05 +0800 Subject: [PATCH 05/17] resolve comments --- .../AspireAppConfigurationExtensions.cs | 73 ++++--------------- .../Aspire.Azure.AppConfiguration/README.md | 6 +- .../AppConfiguratinPublicApiTests.cs | 21 ------ .../AspireAppConfigurationExtensionsTest.cs | 33 --------- 4 files changed, 18 insertions(+), 115 deletions(-) diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs index 777376835da..9f89e5a239d 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -37,7 +37,22 @@ public static void AddAzureAppConfiguration( ArgumentNullException.ThrowIfNull(builder); ArgumentException.ThrowIfNullOrEmpty(connectionName); - AzureAppConfigurationSettings settings = GetSettings(builder.Configuration, connectionName, configureSettings); + IConfigurationSection configSection = builder.Configuration.GetSection(DefaultConfigSectionName); + + var settings = new AzureAppConfigurationSettings(); + configSection.Bind(settings); + + if (builder.Configuration.GetConnectionString(connectionName) is string connectionString) + { + ((IConnectionStringSettings)settings).ParseConnectionString(connectionString); + } + + configureSettings?.Invoke(settings); + + if (settings.Endpoint is null) + { + throw new InvalidOperationException($"Endpoint is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'Endpoint' key in the '{DefaultConfigSectionName}' configuration section."); + } builder.Configuration.AddAzureAppConfiguration( options => @@ -58,60 +73,4 @@ public static void AddAzureAppConfiguration( traceBuilder.AddSource(["Microsoft.Extensions.Configuration.AzureAppConfiguration"])); } } - - /// - /// Adds the Azure App Configuration to be configuration values in the . - /// - /// The to add the secrets to. - /// A name used to retrieve the connection string from the ConnectionStrings configuration section. - /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. - /// An optional method that can be used for customizing the . - /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration. - /// Reads the configuration from "Aspire:Azure:Data:AppConfiguration" section. - /// Thrown when mandatory is not provided. - public static IConfigurationBuilder AddAzureAppConfiguration( - this IConfigurationManager configurationManager, - string connectionName, - Action? configureSettings = null, - Action? configureOptions = null, - bool optional = false) - { - ArgumentNullException.ThrowIfNull(configurationManager); - ArgumentException.ThrowIfNullOrEmpty(connectionName); - - AzureAppConfigurationSettings settings = GetSettings(configurationManager, connectionName, configureSettings); - - return configurationManager.AddAzureAppConfiguration( - options => - { - options.Connect(settings.Endpoint, settings.Credential ?? new DefaultAzureCredential()); - configureOptions?.Invoke(options); - }, - optional); - } - - private static AzureAppConfigurationSettings GetSettings( - IConfiguration configuration, - string connectionName, - Action? configureSettings) - { - IConfigurationSection configSection = configuration.GetSection(DefaultConfigSectionName); - - var settings = new AzureAppConfigurationSettings(); - configSection.Bind(settings); - - if (configuration.GetConnectionString(connectionName) is string connectionString) - { - ((IConnectionStringSettings)settings).ParseConnectionString(connectionString); - } - - configureSettings?.Invoke(settings); - - if (settings.Endpoint is null) - { - throw new InvalidOperationException($"Endpoint is missing. It should be provided in 'ConnectionStrings:{connectionName}' or under the 'Endpoint' key in the '{DefaultConfigSectionName}' configuration section."); - } - - return settings; - } } diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Azure.AppConfiguration/README.md index 82ca8b2d491..cb94f04bfcf 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/README.md +++ b/src/Components/Aspire.Azure.AppConfiguration/README.md @@ -109,10 +109,8 @@ The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.C { "Aspire": { "Azure": { - "Data": { - "AppConfiguration": { - "DisableTracing": false - } + "AppConfiguration": { + "DisableTracing": false } } } diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs index 7cfbd69079a..5f34c6bad14 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs +++ b/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs @@ -21,18 +21,6 @@ public void AddAzureAppConfigurationShouldThrowWhenBuilderIsNull() Assert.Equal(nameof(builder), exception.ParamName); } - [Fact] - public void AddAzureAppConfigurationShouldThrowWhenConfigurationManagerIsNull() - { - IConfigurationManager configurationManager = null!; - const string connectionName = "appConfig"; - - var action = () => configurationManager.AddAzureAppConfiguration(connectionName); - - var exception = Assert.Throws(action); - Assert.Equal(nameof(configurationManager), exception.ParamName); - } - [Theory] [InlineData(true)] [InlineData(false)] @@ -47,14 +35,5 @@ public void AddAzureAppConfigurationShouldThrowWhenConnectionNameIsNullOrEmpty(b ? Assert.Throws(action) : Assert.Throws(action); Assert.Equal(nameof(connectionName), exception.ParamName); - - var configurationManager = new ConfigurationManager(); - - action = () => configurationManager.AddAzureAppConfiguration(connectionName); - - exception = isNull - ? Assert.Throws(action) - : Assert.Throws(action); - Assert.Equal(nameof(connectionName), exception.ParamName); } } diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs b/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs index 60d84d25e99..1a778bf1658 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs +++ b/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs @@ -93,39 +93,6 @@ public void AddsAppConfigurationToApplication() Assert.Equal("test-value-2", builder.Configuration["test-key-2"]); } - [Fact] - public void AddsAppConfigurationToConfiguration() - { - var endpoint = new Uri("https://aspiretests.azconfig.io/"); - var mockTransport = new MockTransport(CreateResponse(""" - { - "items": [ - { - "key": "test-key-1", - "value": "test-value-1" - }, - { - "key": "test-key-2", - "value": "test-value-2" - } - ] - } - """)); - - var configurationManager = new ConfigurationManager(); - configurationManager.AddAzureAppConfiguration( - "appConfig", - settings => - { - settings.Endpoint = endpoint; - settings.Credential = new EmptyTokenCredential(); - }, - options => options.ConfigureClientOptions(clientOptions => clientOptions.Transport = mockTransport)); - - Assert.Equal("test-value-1", configurationManager["test-key-1"]); - Assert.Equal("test-value-2", configurationManager["test-key-2"]); - } - private static MockResponse CreateResponse(string content) { var buffer = Encoding.UTF8.GetBytes(content); From efca9028e94113b6ebabd158bd657097a294d4c9 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 7 May 2025 12:41:16 +0800 Subject: [PATCH 06/17] update --- .../Aspire.Azure.AppConfiguration.csproj | 3 ++- .../AzureAppConfigurationSettings.cs | 2 +- .../Aspire.Azure.AppConfiguration/ConfigurationSchema.json | 2 +- src/Components/Aspire.Azure.AppConfiguration/README.md | 4 ++-- src/Components/Aspire_Components_Progress.md | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj b/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj index 4be18be6eb2..055467fbd12 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj +++ b/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj @@ -9,6 +9,8 @@ $(NoWarn);SYSLIB1100;SYSLIB1101 false + + true @@ -20,7 +22,6 @@ - diff --git a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs index 5015bcd6fb5..279c24e0522 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs @@ -12,7 +12,7 @@ namespace Aspire.Azure.AppConfiguration; public sealed class AzureAppConfigurationSettings : IConnectionStringSettings { /// - /// A to the App Config store on which the client operates. Appears as "Endpoint" in the Azure portal. + /// A to the App Configuration store on which the client operates. Appears as "Endpoint" in the Azure portal. /// This is likely to be similar to "https://{store_name}.azconfig.io". /// public Uri? Endpoint { get; set; } diff --git a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json index 4224107fdd2..6d2b43e0c11 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json +++ b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json @@ -27,7 +27,7 @@ "Endpoint": { "type": "string", "format": "uri", - "description": "A 'System.Uri' to the App Config store on which the client operates. Appears as \"Endpoint\" in the Azure portal. This is likely to be similar to \"https://{store_name}.azconfig.io\"." + "description": "A 'System.Uri' to the App Configuration store on which the client operates. Appears as \"Endpoint\" in the Azure portal. This is likely to be similar to \"https://{store_name}.azconfig.io\"." } }, "description": "Provides the client configuration settings for connecting to Azure App Configuration." diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Azure.AppConfiguration/README.md index cc9819af09e..2535c034279 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/README.md +++ b/src/Components/Aspire.Azure.AppConfiguration/README.md @@ -81,7 +81,7 @@ For information about using the Feature Management library, please go to the [do ## Configuration -The .NET Aspire Azure App Configuration library provides multiple options to configure the Azure App Configuration connection based on the requirements and conventions of your project. Note that the App Config `Endpoint` is required to be supplied. +The .NET Aspire Azure App Configuration library provides multiple options to configure the Azure App Configuration connection based on the requirements and conventions of your project. Note that the App Config endpoint is required to be supplied, either in `AzureAppConfigurationSettings.Endpoint` or using a connection string. ### Use a connection string @@ -91,7 +91,7 @@ When using a connection string from the `ConnectionStrings` configuration sectio builder.AddAzureAppConfiguration("appConfigConnectionName"); ``` -And then the App Config endpoint will be retrieved from the `ConnectionStrings` configuration section. The App Config store URI works with the `AzureAppConfigurationSettings.Credential` property to establish a connection. If no credential is configured, the [DefaultAzureCredential](https://learn.microsoft.com/dotnet/api/azure.identity.defaultazurecredential) is used. +And then the App Configuration endpoint will be retrieved from the `ConnectionStrings` configuration section. The App Configuration store URI works with the `AzureAppConfigurationSettings.Credential` property to establish a connection. If no credential is configured, the [DefaultAzureCredential](https://learn.microsoft.com/dotnet/api/azure.identity.defaultazurecredential) is used. ```json { diff --git a/src/Components/Aspire_Components_Progress.md b/src/Components/Aspire_Components_Progress.md index a35640f5070..e4bf2588358 100644 --- a/src/Components/Aspire_Components_Progress.md +++ b/src/Components/Aspire_Components_Progress.md @@ -12,7 +12,7 @@ As part of the .NET Aspire November preview, we want to include a set of .NET As | Microsoft.EntityFrameworkCore.SqlServer | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | MongoDB.Driver | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.AI.OpenAI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | -| Azure.Data.AppConfiguration | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | +| Azure.AppConfiguration | ✅ | ✅ | ✅ | ✅ | ✅ | | ❌ | | | Azure.Data.Tables | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.Messaging.EventHubs | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | | Azure.Messaging.WebPubSub | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | From 74b7956542c5733f846d03e2728f54b361f1a142 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 7 May 2025 13:16:16 +0800 Subject: [PATCH 07/17] remove all tracing related code --- Aspire.sln | 6 ------ .../Aspire.Azure.AppConfiguration.csproj | 1 + .../AspireAppConfigurationExtensions.cs | 10 ++-------- .../AzureAppConfigurationSettings.cs | 8 -------- .../ConfigurationSchema.json | 5 ----- .../ConfigurationTests.cs | 5 ++--- .../ConformanceTests.cs | 18 ++++++------------ 7 files changed, 11 insertions(+), 42 deletions(-) diff --git a/Aspire.sln b/Aspire.sln index bd9e150a8e4..04a1da35cca 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -661,16 +661,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.Npgsql.EntityF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Components.Common.Tests", "tests\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj", "{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}" EndProject -<<<<<<< HEAD -<<<<<<< HEAD Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration", "src\Components\Aspire.Azure.AppConfiguration\Aspire.Azure.AppConfiguration.csproj", "{C33CE874-27B7-4194-A2E7-D0CD950997CC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration.Tests", "tests\Aspire.Azure.AppConfiguration.Tests\Aspire.Azure.AppConfiguration.Tests.csproj", "{FE972BF3-F448-4CF0-8544-0056B26BF006}" -======= Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject -======= ->>>>>>> 0e182c9de0132af77787fdcc550e19b63a50e5e1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Azure.ContainerRegistry", "src\Aspire.Hosting.Azure.ContainerRegistry\Aspire.Hosting.Azure.ContainerRegistry.csproj", "{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Azure.AppService", "src\Aspire.Hosting.Azure.AppService\Aspire.Hosting.Azure.AppService.csproj", "{5DDF8E89-FBBD-4A6F-BF32-7D2140724941}" @@ -680,7 +675,6 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureAppService.ApiService", "playground\AzureAppService\AzureAppService.ApiService\AzureAppService.ApiService.csproj", "{A617DC84-65DA-41B5-B378-6C2F569CEE48}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureAppService.AppHost", "playground\AzureAppService\AzureAppService.AppHost\AzureAppService.AppHost.csproj", "{2C879943-DF34-44FA-B2C3-29D97F24DD76}" ->>>>>>> aad850bcfea054da216d79cdf54b049740daab95 EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj b/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj index 055467fbd12..689d146e3c5 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj +++ b/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj @@ -22,6 +22,7 @@ + diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs index f5ed9324109..1f64a00615a 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -64,13 +64,7 @@ public static void AddAzureAppConfiguration( builder.Services.AddAzureAppConfiguration(); // register IConfigurationRefresherProvider service - if (!settings.DisableTracing) - { - // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 - // Will be supported in the next 8.2.0 release - builder.Services.AddOpenTelemetry() - .WithTracing(traceBuilder => - traceBuilder.AddSource(["Microsoft.Extensions.Configuration.AzureAppConfiguration"])); - } + // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 + // Tracing will be supported in the next 8.2.0 release } } diff --git a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs index 279c24e0522..2eff5c43856 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs @@ -22,14 +22,6 @@ public sealed class AzureAppConfigurationSettings : IConnectionStringSettings /// public TokenCredential? Credential { get; set; } - /// - /// Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not. - /// - /// - /// The default value is . - /// - public bool DisableTracing { get; set; } - void IConnectionStringSettings.ParseConnectionString(string? connectionString) { if (!string.IsNullOrEmpty(connectionString) && diff --git a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json index 6d2b43e0c11..3e1b6bbc727 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json +++ b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json @@ -19,11 +19,6 @@ "AppConfiguration": { "type": "object", "properties": { - "DisableTracing": { - "type": "boolean", - "description": "Gets or sets a boolean value that indicates whether the OpenTelemetry tracing is disabled or not.", - "default": false - }, "Endpoint": { "type": "string", "format": "uri", diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs index 0826085160a..8b22f938a5d 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs +++ b/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs @@ -11,7 +11,6 @@ public class ConfigurationTests public void EndpointUriIsNullByDefault() => Assert.Null(new AzureAppConfigurationSettings().Endpoint); - [Fact] - public void TracingIsEnabledByDefault() - => Assert.False(new AzureAppConfigurationSettings().DisableTracing); + // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 + // Tracing will be supported in the next 8.2.0 release } diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs index 2a1c3f7a72f..672ac27c8dc 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs +++ b/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs @@ -32,12 +32,8 @@ public class ConformanceTests : ConformanceTests new[] { - ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\""), - ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "http://YOUR_URI", "DisableTracing": "true"}}}}""", "Value is \"string\" but should be \"boolean\""), + ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\"") }; protected override void SetHealthCheck(AzureAppConfigurationSettings options, bool enabled) @@ -98,10 +93,9 @@ protected override void SetTracing(AzureAppConfigurationSettings options, bool e => throw new NotImplementedException(); protected override void TriggerActivity(IConfigurationRefresherProvider service) - { - Thread.Sleep(1000); - service.Refreshers.First().RefreshAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - } + // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 + // Will be supported in the next 8.2.0 release + => throw new NotImplementedException(); [Fact] public void TracingEnablesTheRightActivitySource() From 3adaa0fda0607f78c8f0b2e8890f50b3968c6b48 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 7 May 2025 13:19:12 +0800 Subject: [PATCH 08/17] update readme --- src/Components/Aspire.Azure.AppConfiguration/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Azure.AppConfiguration/README.md index 2535c034279..1c63dec013b 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/README.md +++ b/src/Components/Aspire.Azure.AppConfiguration/README.md @@ -110,7 +110,7 @@ The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.C "Aspire": { "Azure": { "AppConfiguration": { - "DisableTracing": false + "Endpoint": "http://YOUR_URI" } } } @@ -119,10 +119,10 @@ The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.C ### Use inline delegates -You can also pass the `Action configureSettings` delegate to set up some or all the options inline, for example to disable tracing from code: +You can also pass the `Action configureSettings` delegate to set up some or all the options inline, for example to set App Configuration endpoint from code: ```csharp -builder.AddAzureAppConfiguration("appConfig", configureSettings: settings => settings.DisableTracing = true); +builder.AddAzureAppConfiguration("appConfig", configureSettings: settings => settings.Endpoint = "http://YOUR_URI"); ``` ## AppHost extensions From 60faaef7110b44aaa298fa96559ed93b465624da Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Wed, 7 May 2025 16:21:43 +0800 Subject: [PATCH 09/17] update --- .../AspireAppConfigurationExtensions.cs | 3 --- src/Components/Aspire.Azure.AppConfiguration/README.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs index 1f64a00615a..3dc21ac3d5c 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -63,8 +63,5 @@ public static void AddAzureAppConfiguration( optional); builder.Services.AddAzureAppConfiguration(); // register IConfigurationRefresherProvider service - - // WIP: https://github.com/Azure/AppConfiguration-DotnetProvider/pull/645 - // Tracing will be supported in the next 8.2.0 release } } diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Azure.AppConfiguration/README.md index 1c63dec013b..5300f9520a4 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/README.md +++ b/src/Components/Aspire.Azure.AppConfiguration/README.md @@ -110,7 +110,7 @@ The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.C "Aspire": { "Azure": { "AppConfiguration": { - "Endpoint": "http://YOUR_URI" + "Endpoint": "YOUR_APPCONFIGURATION_ENDPOINT_URI" } } } From aaed55f0b2c5c19f352d07dbd9e704e213a3931b Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Thu, 8 May 2025 11:16:57 +0800 Subject: [PATCH 10/17] move Optional parameter to AzureAppConfigurationSettings --- .../AspireAppConfigurationExtensions.cs | 8 +++----- .../AzureAppConfigurationSettings.cs | 6 ++++++ .../ConfigurationSchema.json | 4 ++++ .../ConformanceTests.cs | 10 ++++++---- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs index 3dc21ac3d5c..606830b646c 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs @@ -24,15 +24,13 @@ public static class AspireAppConfigurationExtensions /// A name used to retrieve the connection string from the ConnectionStrings configuration section. /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. /// An optional method that can be used for customizing the . - /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no settings are populated from Azure App Configuration. - /// Reads the configuration from "Aspire:Azure:AppConfiguration" section. + /// Reads the settings from "Aspire:Azure:AppConfiguration" section. /// Thrown when mandatory is not provided. public static void AddAzureAppConfiguration( this IHostApplicationBuilder builder, string connectionName, Action? configureSettings = null, - Action? configureOptions = null, - bool optional = false) + Action? configureOptions = null) { ArgumentNullException.ThrowIfNull(builder); ArgumentException.ThrowIfNullOrEmpty(connectionName); @@ -60,7 +58,7 @@ public static void AddAzureAppConfiguration( options.Connect(settings.Endpoint, settings.Credential ?? new DefaultAzureCredential()); configureOptions?.Invoke(options); }, - optional); + settings.Optional); builder.Services.AddAzureAppConfiguration(); // register IConfigurationRefresherProvider service } diff --git a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs index 2eff5c43856..0922504f79b 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs +++ b/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs @@ -22,6 +22,12 @@ public sealed class AzureAppConfigurationSettings : IConnectionStringSettings /// public TokenCredential? Credential { get; set; } + /// + /// Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. + /// If false, the exception is thrown. If true, the exception is suppressed and no configuration values are populated from Azure App Configuration. + /// + public bool Optional { get; set; } + void IConnectionStringSettings.ParseConnectionString(string? connectionString) { if (!string.IsNullOrEmpty(connectionString) && diff --git a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json index 3e1b6bbc727..82afb13d09c 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json +++ b/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json @@ -23,6 +23,10 @@ "type": "string", "format": "uri", "description": "A 'System.Uri' to the App Configuration store on which the client operates. Appears as \"Endpoint\" in the Azure portal. This is likely to be similar to \"https://{store_name}.azconfig.io\"." + }, + "Optional": { + "type": "boolean", + "description": "Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no configuration values are populated from Azure App Configuration." } }, "description": "Provides the client configuration settings for connecting to Azure App Configuration." diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs b/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs index 672ac27c8dc..9effd348ab2 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs +++ b/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs @@ -33,7 +33,8 @@ public class ConformanceTests : ConformanceTests { @@ -71,13 +73,13 @@ protected override void RegisterComponent(HostApplicationBuilder builder, Action startupOptions.Timeout = TimeSpan.FromSeconds(1); }); options.ConfigureClientOptions(clientOptions => clientOptions.Retry.MaxRetries = 0); - }, - optional: true); + }); } protected override (string json, string error)[] InvalidJsonToErrorMessage => new[] { - ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\"") + ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\""), + ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "http://YOUR_URI", "Optional": "true"}}}}""", "Value is \"string\" but should be \"boolean\"") }; protected override void SetHealthCheck(AzureAppConfigurationSettings options, bool enabled) From 2b6d0131e5500794749dfc2ef8e1d082503d1d11 Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 12 May 2025 12:59:02 +0800 Subject: [PATCH 11/17] upgrade app config package version --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index c82adf37a39..7a3760073a9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -25,7 +25,7 @@ - + From ec6a5df4e86bed2388897376785801ae9b2a6d6b Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 12 May 2025 13:10:49 +0800 Subject: [PATCH 12/17] revert unintended change --- Aspire.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/Aspire.sln b/Aspire.sln index d0f4d9342f5..603693a9488 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -663,7 +663,6 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration", "src\Components\Aspire.Azure.AppConfiguration\Aspire.Azure.AppConfiguration.csproj", "{C33CE874-27B7-4194-A2E7-D0CD950997CC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration.Tests", "tests\Aspire.Azure.AppConfiguration.Tests\Aspire.Azure.AppConfiguration.Tests.csproj", "{FE972BF3-F448-4CF0-8544-0056B26BF006}" -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Azure.ContainerRegistry", "src\Aspire.Hosting.Azure.ContainerRegistry\Aspire.Hosting.Azure.ContainerRegistry.csproj", "{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}" EndProject From 2fe8a0886d9957b678ea48ca06d2053a32ea94bd Mon Sep 17 00:00:00 2001 From: Zhiyuan Liang Date: Mon, 12 May 2025 13:37:09 +0800 Subject: [PATCH 13/17] change package name to Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration --- Aspire.sln | 4 ++-- ...oft.Extensions.Configuration.AzureAppConfiguration.csproj} | 0 .../AspireAppConfigurationExtensions.cs | 2 +- .../AssemblyInfo.cs | 2 +- .../AzureAppConfigurationSettings.cs | 2 +- .../ConfigurationSchema.json | 0 .../README.md | 4 ++-- src/Components/Aspire_Components_Progress.md | 2 +- .../AppConfiguratinPublicApiTests.cs | 4 ++-- ...tensions.Configuration.AzureAppConfiguration.Tests.csproj} | 4 ++-- .../AspireAppConfigurationExtensionsTest.cs | 3 ++- .../ConfigurationTests.cs | 2 +- .../ConformanceTests.cs | 2 +- 13 files changed, 16 insertions(+), 15 deletions(-) rename src/Components/{Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj} (100%) rename src/Components/{Aspire.Azure.AppConfiguration => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration}/AspireAppConfigurationExtensions.cs (97%) rename src/Components/{Aspire.Azure.AppConfiguration => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration}/AssemblyInfo.cs (83%) rename src/Components/{Aspire.Azure.AppConfiguration => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration}/AzureAppConfigurationSettings.cs (95%) rename src/Components/{Aspire.Azure.AppConfiguration => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration}/ConfigurationSchema.json (100%) rename src/Components/{Aspire.Azure.AppConfiguration => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration}/README.md (97%) rename tests/{Aspire.Azure.AppConfiguration.Tests => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests}/AppConfiguratinPublicApiTests.cs (90%) rename tests/{Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests.csproj} (57%) rename tests/{Aspire.Azure.AppConfiguration.Tests => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests}/AspireAppConfigurationExtensionsTest.cs (97%) rename tests/{Aspire.Azure.AppConfiguration.Tests => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests}/ConfigurationTests.cs (84%) rename tests/{Aspire.Azure.AppConfiguration.Tests => Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests}/ConformanceTests.cs (98%) diff --git a/Aspire.sln b/Aspire.sln index 603693a9488..e7e9c5ca25a 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -660,9 +660,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.Npgsql.EntityF EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Components.Common.Tests", "tests\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj", "{30950CEB-2232-F9FC-04FF-ADDCB8AC30A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration", "src\Components\Aspire.Azure.AppConfiguration\Aspire.Azure.AppConfiguration.csproj", "{C33CE874-27B7-4194-A2E7-D0CD950997CC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration", "src\Components\Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration\Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj", "{C33CE874-27B7-4194-A2E7-D0CD950997CC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Azure.AppConfiguration.Tests", "tests\Aspire.Azure.AppConfiguration.Tests\Aspire.Azure.AppConfiguration.Tests.csproj", "{FE972BF3-F448-4CF0-8544-0056B26BF006}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests", "tests\Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests\Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests.csproj", "{FE972BF3-F448-4CF0-8544-0056B26BF006}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Hosting.Azure.ContainerRegistry", "src\Aspire.Hosting.Azure.ContainerRegistry\Aspire.Hosting.Azure.ContainerRegistry.csproj", "{6CBA29C8-FF78-4ABC-BEFA-2A53CB4DB2A3}" EndProject diff --git a/src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj similarity index 100% rename from src/Components/Aspire.Azure.AppConfiguration/Aspire.Azure.AppConfiguration.csproj rename to src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.csproj diff --git a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs similarity index 97% rename from src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs rename to src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs index 606830b646c..94d63dc1522 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Aspire.Azure.AppConfiguration; +using Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration; using Azure.Identity; using Microsoft.Extensions.Configuration.AzureAppConfiguration; using Microsoft.Extensions.Configuration; diff --git a/src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs similarity index 83% rename from src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs rename to src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs index da7f647a848..cad1156fbcb 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AssemblyInfo.cs +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Aspire; -using Aspire.Azure.AppConfiguration; +using Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration; [assembly: ConfigurationSchema("Aspire:Azure:AppConfiguration", typeof(AzureAppConfigurationSettings))] diff --git a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationSettings.cs similarity index 95% rename from src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs rename to src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationSettings.cs index 0922504f79b..446639687a9 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/AzureAppConfigurationSettings.cs +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationSettings.cs @@ -4,7 +4,7 @@ using Aspire.Azure.Common; using Azure.Core; -namespace Aspire.Azure.AppConfiguration; +namespace Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration; /// /// Provides the client configuration settings for connecting to Azure App Configuration. diff --git a/src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSchema.json similarity index 100% rename from src/Components/Aspire.Azure.AppConfiguration/ConfigurationSchema.json rename to src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSchema.json diff --git a/src/Components/Aspire.Azure.AppConfiguration/README.md b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md similarity index 97% rename from src/Components/Aspire.Azure.AppConfiguration/README.md rename to src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md index 5300f9520a4..f803db47881 100644 --- a/src/Components/Aspire.Azure.AppConfiguration/README.md +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md @@ -1,4 +1,4 @@ -# Aspire.Azure.AppConfiguration +# Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration Retrieves configuration settings from Azure App Configuration to use in your application. Registers Azure App Configuration service as a configuration source. Enables corresponding logging and telemetry. @@ -14,7 +14,7 @@ Retrieves configuration settings from Azure App Configuration to use in your app Install the .NET Aspire Azure App Configuration library with [NuGet](https://www.nuget.org): ```dotnetcli -dotnet add package Aspire.Azure.AppConfiguration +dotnet add package Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration ``` ## Usage examples diff --git a/src/Components/Aspire_Components_Progress.md b/src/Components/Aspire_Components_Progress.md index 0612912075e..752180d6da0 100644 --- a/src/Components/Aspire_Components_Progress.md +++ b/src/Components/Aspire_Components_Progress.md @@ -10,9 +10,9 @@ These integrations should follow the [.NET Aspire Integration Requirements](#net | Microsoft.Data.SqlClient | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | | Microsoft.EntityFramework.Cosmos | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | | Microsoft.EntityFrameworkCore.SqlServer | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | +| Microsoft.Extensions.Configuration.AzureAppConfiguration | ✅ | ✅ | ✅ | ✅ | ✅ | | ❌ | | | MongoDB.Driver | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.AI.OpenAI | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | -| Azure.AppConfiguration | ✅ | ✅ | ✅ | ✅ | ✅ | | ❌ | | | Azure.Data.Tables | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | | Azure.Messaging.EventHubs | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | | Azure.Messaging.WebPubSub | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AppConfiguratinPublicApiTests.cs similarity index 90% rename from tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs rename to tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AppConfiguratinPublicApiTests.cs index 5f34c6bad14..ab6f9a7ebf0 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/AppConfiguratinPublicApiTests.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AppConfiguratinPublicApiTests.cs @@ -5,9 +5,9 @@ using Microsoft.Extensions.Hosting; using Xunit; -namespace Aspire.Azure.AppConfiguration.Tests; +namespace Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests; -public class DataAppConfigurationPublicApiTests +public class AppConfigurationPublicApiTests { [Fact] public void AddAzureAppConfigurationShouldThrowWhenBuilderIsNull() diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests.csproj similarity index 57% rename from tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj rename to tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests.csproj index ca93ee8de90..e76ed140c1d 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/Aspire.Azure.AppConfiguration.Tests.csproj +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests.csproj @@ -5,7 +5,7 @@ - + @@ -14,7 +14,7 @@ - + diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs similarity index 97% rename from tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs rename to tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs index 24281726f1b..648fd8f5b3a 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Aspire.Azure; using Azure.Core; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using System.Text; using Xunit; -namespace Aspire.Azure.AppConfiguration.Tests; +namespace Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests; public class AspireAppConfigurationExtensionsTest { diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConfigurationTests.cs similarity index 84% rename from tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs rename to tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConfigurationTests.cs index 8b22f938a5d..49ee121899f 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/ConfigurationTests.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConfigurationTests.cs @@ -3,7 +3,7 @@ using Xunit; -namespace Aspire.Azure.AppConfiguration.Tests; +namespace Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests; public class ConfigurationTests { diff --git a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs similarity index 98% rename from tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs rename to tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs index 9effd348ab2..d439b566d83 100644 --- a/tests/Aspire.Azure.AppConfiguration.Tests/ConformanceTests.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs @@ -12,7 +12,7 @@ using System.Reflection; using Xunit; -namespace Aspire.Azure.AppConfiguration.Tests; +namespace Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests; public class ConformanceTests : ConformanceTests { From 91791405219342d8c8efbf74f495f37ae8020052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Mon, 12 May 2025 09:26:33 -0700 Subject: [PATCH 14/17] Update telemetry.md --- .../README.md | 2 +- src/Components/Telemetry.md | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md index f803db47881..67aefe4c7fc 100644 --- a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md @@ -1,6 +1,6 @@ # Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration -Retrieves configuration settings from Azure App Configuration to use in your application. Registers Azure App Configuration service as a configuration source. Enables corresponding logging and telemetry. +Retrieves configuration settings from Azure App Configuration to use in your application. Registers Azure App Configuration service as a configuration source. Enables corresponding logging. ## Getting started diff --git a/src/Components/Telemetry.md b/src/Components/Telemetry.md index c19559187e9..1d0b979517c 100644 --- a/src/Components/Telemetry.md +++ b/src/Components/Telemetry.md @@ -181,6 +181,14 @@ Aspire.Microsoft.EntityFrameworkCore.SqlServer: - Metric names: - none +Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration +- Log categories: + "Microsoft.Extensions.Configuration.AzureAppConfiguration.Refresh" +- Activity source names: + - none +- Metric names: + - none + Aspire.Milvus.Client: - Log categories: "Milvus.Client" From d8c27378d00885ac66af212098bafdc3c4de0ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Mon, 12 May 2025 09:40:51 -0700 Subject: [PATCH 15/17] Update configuration sectopm --- .../AspireAppConfigurationExtensions.cs | 4 +-- .../AssemblyInfo.cs | 2 +- .../ConfigurationSchema.json | 34 ++++++++++++------- .../README.md | 13 ++++--- .../AspireAppConfigurationExtensionsTest.cs | 2 +- .../ConformanceTests.cs | 7 ++-- 6 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs index 94d63dc1522..803d90e466a 100644 --- a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AspireAppConfigurationExtensions.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.Hosting; /// public static class AspireAppConfigurationExtensions { - internal const string DefaultConfigSectionName = "Aspire:Azure:AppConfiguration"; + internal const string DefaultConfigSectionName = "Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration"; /// /// Adds the Azure App Configuration to be configuration in the . @@ -24,7 +24,7 @@ public static class AspireAppConfigurationExtensions /// A name used to retrieve the connection string from the ConnectionStrings configuration section. /// An optional method that can be used for customizing the . It's invoked after the settings are read from the configuration. /// An optional method that can be used for customizing the . - /// Reads the settings from "Aspire:Azure:AppConfiguration" section. + /// Reads the settings from "Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration" section. /// Thrown when mandatory is not provided. public static void AddAzureAppConfiguration( this IHostApplicationBuilder builder, diff --git a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs index cad1156fbcb..1021be2c2ab 100644 --- a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/AssemblyInfo.cs @@ -4,7 +4,7 @@ using Aspire; using Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration; -[assembly: ConfigurationSchema("Aspire:Azure:AppConfiguration", typeof(AzureAppConfigurationSettings))] +[assembly: ConfigurationSchema("Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration", typeof(AzureAppConfigurationSettings))] [assembly: LoggingCategories( "Microsoft.Extensions.Configuration.AzureAppConfiguration.Refresh")] diff --git a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSchema.json b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSchema.json index 82afb13d09c..248a28ed985 100644 --- a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSchema.json +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/ConfigurationSchema.json @@ -13,23 +13,33 @@ "Aspire": { "type": "object", "properties": { - "Azure": { + "Microsoft": { "type": "object", "properties": { - "AppConfiguration": { + "Extensions": { "type": "object", "properties": { - "Endpoint": { - "type": "string", - "format": "uri", - "description": "A 'System.Uri' to the App Configuration store on which the client operates. Appears as \"Endpoint\" in the Azure portal. This is likely to be similar to \"https://{store_name}.azconfig.io\"." - }, - "Optional": { - "type": "boolean", - "description": "Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no configuration values are populated from Azure App Configuration." + "Configuration": { + "type": "object", + "properties": { + "AzureAppConfiguration": { + "type": "object", + "properties": { + "Endpoint": { + "type": "string", + "format": "uri", + "description": "A 'System.Uri' to the App Configuration store on which the client operates. Appears as \"Endpoint\" in the Azure portal. This is likely to be similar to \"https://{store_name}.azconfig.io\"." + }, + "Optional": { + "type": "boolean", + "description": "Determines the behavior of the App Configuration provider when an exception occurs while loading data from server. If false, the exception is thrown. If true, the exception is suppressed and no configuration values are populated from Azure App Configuration." + } + }, + "description": "Provides the client configuration settings for connecting to Azure App Configuration." + } + } } - }, - "description": "Provides the client configuration settings for connecting to Azure App Configuration." + } } } } diff --git a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md index 67aefe4c7fc..10639f1ec9f 100644 --- a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md @@ -103,14 +103,19 @@ And then the App Configuration endpoint will be retrieved from the `ConnectionSt ### Use configuration providers -The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `AzureAppConfigurationSettings` from configuration by using the `Aspire:Azure:AppConfiguration` key. Example `appsettings.json` that configures some of the options: +The .NET Aspire Azure App Configuration library supports [Microsoft.Extensions.Configuration](https://learn.microsoft.com/dotnet/api/microsoft.extensions.configuration). It loads the `AzureAppConfigurationSettings` from configuration by using the `Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration` key. Example `appsettings.json` that configures some of the options: ```json { + Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration "Aspire": { - "Azure": { - "AppConfiguration": { - "Endpoint": "YOUR_APPCONFIGURATION_ENDPOINT_URI" + "Microsoft": { + "Extensions": { + "Configuration": { + "AzureAppConfiguration": { + "Endpoint": "YOUR_APPCONFIGURATION_ENDPOINT_URI" + } + } } } } diff --git a/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs index 648fd8f5b3a..6db1a5e8cd0 100644 --- a/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/AspireAppConfigurationExtensionsTest.cs @@ -43,7 +43,7 @@ public void ConnectionNameWinsOverConfiguration() var mockTransport = new MockTransport(CreateResponse("""{}""")); var builder = Host.CreateEmptyApplicationBuilder(null); builder.Configuration.AddInMemoryCollection([ - new KeyValuePair(ConformanceTests.CreateConfigKey("Aspire:Azure:AppConfiguration", null, "Endpoint"), "https://unused.azconfig.io/"), + new KeyValuePair(ConformanceTests.CreateConfigKey("Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration", null, "Endpoint"), "https://unused.azconfig.io/"), new KeyValuePair("ConnectionStrings:appConfig", ConformanceTests.Endpoint) ]); diff --git a/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs index d439b566d83..6cbd055b1d1 100644 --- a/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -//using System.Text; using Aspire.Components.ConformanceTests; using Azure.Core; using Microsoft.DotNet.RemoteExecutor; @@ -44,7 +43,7 @@ public class ConformanceTests : ConformanceTests configuration.AddInMemoryCollection(new KeyValuePair[] { - new(CreateConfigKey("Aspire:Azure:AppConfiguration", null, "Endpoint"), Endpoint) + new(CreateConfigKey("Aspire:Microsoft:Extensions:Configuration:AzureAppConfiguration", null, "Endpoint"), Endpoint) }); protected override void RegisterComponent(HostApplicationBuilder builder, Action? configure = null, string? key = null) @@ -78,8 +77,8 @@ protected override void RegisterComponent(HostApplicationBuilder builder, Action protected override (string json, string error)[] InvalidJsonToErrorMessage => new[] { - ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\""), - ("""{"Aspire": { "Azure": { "AppConfiguration": { "Endpoint": "http://YOUR_URI", "Optional": "true"}}}}""", "Value is \"string\" but should be \"boolean\"") + ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\""), + ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "http://YOUR_URI", "Optional": "true"}}}}""", "Value is \"string\" but should be \"boolean\"") }; protected override void SetHealthCheck(AzureAppConfigurationSettings options, bool enabled) From 1349c14e9668d0fd4aecf2685518c6f2734e8653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Mon, 12 May 2025 09:44:37 -0700 Subject: [PATCH 16/17] Fix json format --- .../ConformanceTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs index 6cbd055b1d1..dffa9c1c517 100644 --- a/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs +++ b/tests/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests/ConformanceTests.cs @@ -77,8 +77,8 @@ protected override void RegisterComponent(HostApplicationBuilder builder, Action protected override (string json, string error)[] InvalidJsonToErrorMessage => new[] { - ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "YOUR_URI"}}}}""", "Value does not match format \"uri\""), - ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "http://YOUR_URI", "Optional": "true"}}}}""", "Value is \"string\" but should be \"boolean\"") + ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "YOUR_URI"}}}}}}""", "Value does not match format \"uri\""), + ("""{"Aspire": { "Microsoft": { "Extensions": { "Configuration": { "AzureAppConfiguration": { "Endpoint": "http://YOUR_URI", "Optional": "true"}}}}}}""", "Value is \"string\" but should be \"boolean\"") }; protected override void SetHealthCheck(AzureAppConfigurationSettings options, bool enabled) From b6e7a382401aea0c4b1811634d406d3d6e477ebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Mon, 12 May 2025 10:21:42 -0700 Subject: [PATCH 17/17] Simplify sample in readme --- .../README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md index 10639f1ec9f..2038a3be9e8 100644 --- a/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md +++ b/src/Components/Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration/README.md @@ -142,9 +142,7 @@ Then, in the _Program.cs_ file of `AppHost`, add a App Configuration connection ```csharp // Service registration -var appConfig = builder.ExecutionContext.IsPublishMode - ? builder.AddAzureAppConfiguration("appConfig") - : builder.AddConnectionString("appConfig"); +var appConfig = builder.AddAzureAppConfiguration("appConfig"); // Service consumption var myService = builder.AddProject()