✅ Overriding IConfigurationManager.GetSection for integration tests in .NET 8 Minimal API

Hi all, In my application, I am optionally configuring Azure App Configuration. This is achieved by checking whether or not an endpoint has been provided in the configuration via IConfigurationManager (see code below). So far in my integration tests, I'm using WebApplicationFactory to modify the services to external services. The problem that I'm encountering is that the value for the options are populated in appsettings.Development.json. Ideally, I'd like to remove it from configuration prior to attempting to see if the endpoint is present. However, the approached that I've tried so far haven't yielded results; - If I create a custom WebApplicationFactory and override ConfigureWebHost to specify AppConfig:Endpoint to null, it stops attempting to connect to Azure App Configuration, but then it fails validation of the option. - The configuration occurs before WebApplicationFactory.ConfigureTestServices is called, so any manipulation of the option there would fail.
18 Replies
MetallixBrother
MetallixBrotherOP5w ago
Program.cs
var builder = WebApplication.CreateBuilder(args);
// code omitted
builder.Configuration.AddAzureAppConfiguration();

builder.Services.AddWorkerOptions();
// code omitted
await app.RunAsync();
var builder = WebApplication.CreateBuilder(args);
// code omitted
builder.Configuration.AddAzureAppConfiguration();

builder.Services.AddWorkerOptions();
// code omitted
await app.RunAsync();
AppConfigOptions.cs
public sealed class AppConfigOptions
{
public const string ConfigurationSectionName = "AppConfig";

[Required]
public required Uri Endpoint { get; init; }
}
public sealed class AppConfigOptions
{
public const string ConfigurationSectionName = "AppConfig";

[Required]
public required Uri Endpoint { get; init; }
}
ConfigurationManagerExtensions.cs
internal static class ConfigurationManagerExtensions
{
public static IConfigurationManager AddAzureAppConfiguration(this IConfigurationManager builder)
{
var appConfig = builder.GetSection(AppConfigOptions.ConfigurationSectionName).Get<AppConfigOptions>();

if (appConfig?.Endpoint is null)
{
return builder;
}

var appConfigUri = appConfig.Endpoint;

if (appConfigUri.IsAbsoluteUri && (appConfigUri.IsHttp() || appConfigUri.IsHttps()))
{
var credentials = new DefaultAzureCredential();

builder.AddAzureAppConfiguration(SetupAzureAppConfiguration(appConfigUri, credentials));
}

return builder;
}

[ExcludeFromCodeCoverage]
private static Action<AzureAppConfigurationOptions> SetupAzureAppConfiguration(Uri appConfigEndpoint, Azure.Core.TokenCredential credential) => options =>
{
options.Connect(appConfigEndpoint, credential)
.UseFeatureFlags()
.ConfigureKeyVault(keyVaultOptions =>
{
keyVaultOptions.SetCredential(credential);
});
};
}
internal static class ConfigurationManagerExtensions
{
public static IConfigurationManager AddAzureAppConfiguration(this IConfigurationManager builder)
{
var appConfig = builder.GetSection(AppConfigOptions.ConfigurationSectionName).Get<AppConfigOptions>();

if (appConfig?.Endpoint is null)
{
return builder;
}

var appConfigUri = appConfig.Endpoint;

if (appConfigUri.IsAbsoluteUri && (appConfigUri.IsHttp() || appConfigUri.IsHttps()))
{
var credentials = new DefaultAzureCredential();

builder.AddAzureAppConfiguration(SetupAzureAppConfiguration(appConfigUri, credentials));
}

return builder;
}

[ExcludeFromCodeCoverage]
private static Action<AzureAppConfigurationOptions> SetupAzureAppConfiguration(Uri appConfigEndpoint, Azure.Core.TokenCredential credential) => options =>
{
options.Connect(appConfigEndpoint, credential)
.UseFeatureFlags()
.ConfigureKeyVault(keyVaultOptions =>
{
keyVaultOptions.SetCredential(credential);
});
};
}
Unknown User
Unknown User5w ago
Message Not Public
Sign In & Join Server To View
MetallixBrother
MetallixBrotherOP5w ago
I tried that. Part of the problem is that the options class is structured. If I set AppConfig:Endpoint to null using an in memory collection, you'll hit the problem mentioned in the first bullet point (i.e. it will skip trying to do Azure App Configuration, but fail validation). Right now there is validation on the option class, which will pass either if the instance is specified (non-null) and Endpoint is also non-null, or if there is no instance of AppConfigOptions.
Sossenbinder
Sossenbinder5w ago
Where does the validation happen which breaks? Can't see it in the snippets right away
MetallixBrother
MetallixBrotherOP5w ago
Apologies, I'll add that shortly, but it's using the validation methodology provided by Microsoft for the options pattern.
Unknown User
Unknown User5w ago
Message Not Public
Sign In & Join Server To View
MetallixBrother
MetallixBrotherOP5w ago
ServiceCollectionExtensions.cs
internal static class ServiceCollectionExtensions
{
[ExcludeFromCodeCoverage]
public static IServiceCollection AddWorkerOptions(this IServiceCollection services)
{
services
.AddOptionsWithValidateOnStart<AppConfigOptions>()
.BindConfiguration(AppConfigOptions.ConfigurationSectionName)
.ValidateDataAnnotations();

// code omitted

return services;
}
// code omitted
}
internal static class ServiceCollectionExtensions
{
[ExcludeFromCodeCoverage]
public static IServiceCollection AddWorkerOptions(this IServiceCollection services)
{
services
.AddOptionsWithValidateOnStart<AppConfigOptions>()
.BindConfiguration(AppConfigOptions.ConfigurationSectionName)
.ValidateDataAnnotations();

// code omitted

return services;
}
// code omitted
}
I can try to create a cloneable repo, but I would need to pare it down significantly as this is software being developed for work. I had looked at PostConfigure, but I think that it still encounters the same problems as previously mentioned.
Unknown User
Unknown User5w ago
Message Not Public
Sign In & Join Server To View
MetallixBrother
MetallixBrotherOP5w ago
https://github.com/ntyrrell/azure-app-config-problem I've set up a very basic implementation as suggested. Thank you for the suggestion, I hope that this will make things clearer.
GitHub
GitHub - ntyrrell/azure-app-config-problem: Using this repo to demo...
Using this repo to demonstrate an issue I'm having with configuring azure app configuration optionally. - ntyrrell/azure-app-config-problem
Unknown User
Unknown User5w ago
Message Not Public
Sign In & Join Server To View
MetallixBrother
MetallixBrotherOP5w ago
I'll give that a go shortly, thank you for looking into this I'm afraid that I'm a bit lost as to what you mean here; what is the purpose of configuration if not to read it?
MetallixBrother
MetallixBrotherOP5w ago
In my defence, I was following the guidelines provided by Microsoft: https://learn.microsoft.com/en-us/dotnet/core/extensions/options#options-validation
Options pattern - .NET
Learn the options pattern to represent groups of related settings in .NET apps. The options pattern uses classes to provide strongly-typed access to settings.
MetallixBrother
MetallixBrotherOP5w ago
I don't believe that's strictly true - in that method, I'm adding options classes using the Option pattern. Then in AddAzureAppConfiguration, I'm parsing the configuration to get the relevant configuration details. Regarding reading the configuration directly, and having to manually parse string values into Uris - I'm confused as to why this is not an antipattern, but retrieving only the pertinent details and parsing it once is an antipattern.
Unknown User
Unknown User5w ago
Message Not Public
Sign In & Join Server To View
MetallixBrother
MetallixBrotherOP5w ago
Thank you, much appreciated. The "optional" path seems to have done the trick (no idea how I missed that before). Thanks again for your assistance!
Unknown User
Unknown User4w ago
Message Not Public
Sign In & Join Server To View
MODiX
MODiX4w ago
If you have no further questions, please use /close to mark the forum thread as answered
Unknown User
Unknown User4w ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?