Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] AzureClientServiceCollectionExtensions.AddAzureClients attempts to read from config values before validation has occurred #48584

Open
halestock opened this issue Mar 5, 2025 · 3 comments
Labels
Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. Extensions ASP.NET Core extensions feature-request This issue requires a new behavior in the product in order be resolved.
Milestone

Comments

@halestock
Copy link

halestock commented Mar 5, 2025

Library name and version

Microsoft.Extensions.Azure 1.10.0

Describe the bug

In our application we use the Options pattern for strongly-typed config, with validation:

var options = builder.Services
  .AddOptionsWithValidateOnStart<ApplicationConfig>()
  .ValidateDataAnnotations()
  .Bind(builder.Configuration);

We then reference it during application startup using the following code:

var config = builder.Configuration.Get<ApplicationConfig>();

We then use the AddAzureClients method to register azure clients using the config object:

services.AddAzureClients(x =>
{
    x.UseCredential(new DefaultAzureCredential());
    x.AddBlobServiceClient(new Uri(config.BlobStorageUrl));
});

However, the lambda that is passed as the Action<AzureClientFactoryBuilder> configureClients parameter is immediately invoked, and so if e.g. config.BlobStorageUrl is null, a ArgumentNullException will be thrown. I would normally expect this to occur after validation has happened, so that we would instead get a validation failure that the required config key is missing or invalid.

Expected behavior

Normally, when injecting clients, the configure action is not invoked until after the StartupValidator has run and successfully validated the config options. This is the case when normally injecting services, e.g.

services.AddDbContextFactory<ApplicationDbContext>(dbOptions =>
{
    dbOptions.UseSqlServer(config.SqlDbConnectionString);
}

In that case, if a required config value were missing, we would see e.g.

Microsoft.Extensions.Options.OptionsValidationException: DataAnnotation validation failed for 'ApplicationConfig' members: 'BlobStorageUrl' with the error: 'The BlobStorageUrl field is required.'

Actual behavior

On startup, an ArgumentNullException is thrown without reference to the specific config value that is missing:

System.ArgumentNullException: Value cannot be null. (Parameter 'uriString')

Reproduction Steps

Abbreviated Program.cs:

var builder = WebApplication.CreateBuilder(args);
var options = builder.Services
  .AddOptionsWithValidateOnStart<ApplicationConfig>()
  .ValidateDataAnnotations()
  .Bind(builder.Configuration);

var config = builder.Configuration.Get<ApplicationConfig>();

builder.Services.AddAzureClients(x =>
{
  x.AddBlobServiceClient(new Uri(config.BlobStorageUrl));
});

var app = builder.Build();

// other startup code

app.Run();

public class ApplicationConfig
{
  [Required]
  [ConfigurationKeyName("BlobStorageUrl")]
  public required string BlobStorageUrl { get; init; }
}

appsettings.json:

{
  "BlobStorageUrl": null
}

Environment

.NET SDK:
 Version:           9.0.100
 Commit:            59db016f11
 Workload version:  9.0.100-manifests.3068a692
 MSBuild version:   17.12.7+5b8665660

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.26100
 OS Platform: Windows
 RID:         win-x64

Microsoft Visual Studio Enterprise 2022 (64-bit) - Current
Version 17.11.6

@github-actions github-actions bot added customer-reported Issues that are reported by GitHub users external to the Azure organization. needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that labels Mar 5, 2025
@jsquire jsquire assigned jsquire and unassigned jsquire Mar 6, 2025
@jsquire jsquire added Client This issue points to a problem in the data-plane of the library. feature-request This issue requires a new behavior in the product in order be resolved. Extensions ASP.NET Core extensions and removed question The issue doesn't require a change to the product in order to be resolved. Most issues start as that labels Mar 6, 2025
@github-actions github-actions bot removed the needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. label Mar 6, 2025
@jsquire
Copy link
Member

jsquire commented Mar 6, 2025

Hi @halestock. Thanks for reaching out and we regret that you're experiencing difficulties. The Microsoft.Extensions.Azure infrastructure was built to support the state of dependency injection and configuration for early versions of .NET Core and does not have awareness of many of the newer features of the ecosystem liked keyed registration and configuration validation. Because of the early state, the Azure extensions are built on custom code for much of the functionality that has since been implemented by the .NET runtime libraries.

As a result, deeper integration with the ecosystem requires more investment than we believe is reasonable for an aging infrastructure. We're currently working to redesign our integration points with configuration and dependency injection, moving it from an Azure extensions package into the structure of the clients themselves. I'm going to move this to the backlog so that we can take this scenario into account when we start looking to design vNext.

@jsquire
Copy link
Member

jsquire commented Mar 6, 2025

//cc: @m-nash

@jsquire jsquire added this to the Backlog milestone Mar 6, 2025
@jonmeyerson
Copy link

jonmeyerson commented Mar 7, 2025

I've worked around a similar problem by not using the extension methods that come with Azure.Storage.Blobs / Azure.Messaging.ServiceBus and directly using AddClient

A quick example:
factoryBuilder.AddBlobServiceClient(configuration.GetConnectionString("AzureWebJobsStorage"));
vs

factoryBuilder.AddClient<BlobServiceClient, BlobClientOptions>((options, serviceProvider) =>
{
      var configuration = serviceProvider.GetRequiredService<IConfiguration>();
//override options as needed. 
     return new BlobServiceClient(configuration.GetConnectionString("AzureWebJobsStorage"), options);
}

To me this is just a matter of there could be more extension methods such as

public static IAzureClientBuilder<BlobServiceClient, ServiceBusClientOptions> AddBlobServiceClient<TBuilder>(this TBuilder builder, Func<IServiceProvider, string> connectionStringResolver)
    where TBuilder : IAzureClientFactoryBuilder
{
    return builder.RegisterClientFactory<BlobServiceClient, BlobClientOptions>((options, provider) =>
    {
        var connectionString = connectionStringResolver(provider);
        return new BlobServiceClient(connectionString, options);
    });
}

and add to the IAzureClientFactoryBuilder and its implementation the ability to access the IServiceProvider overload.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. Extensions ASP.NET Core extensions feature-request This issue requires a new behavior in the product in order be resolved.
Projects
None yet
Development

No branches or pull requests

3 participants