How to inject or use IConfiguration in Azure Function V3 with Dependency Injection when configuring a service
The linked example is poorly designed (In My Opinion). It encourages tight coupling and the mixing of async-await and blocking calls.
IConfiguration
is added to the service collection by default as part of the start up, so I would suggest changing up your design to take advantage of the deferred resolution of dependencies so that the IConfiguration
can be resolved via the built IServiceProvider
using a factory delegate.
public static class BootstrapCosmosDbClient {
private static event EventHandler initializeDatabase = delegate { };
public static IServiceCollection AddCosmosDbService(this IServiceCollection services) {
Func<IServiceProvider, ICosmosDbService> factory = (sp) => {
//resolve configuration
IConfiguration configuration = sp.GetService<IConfiguration>();
//and get the configured settings (Microsoft.Extensions.Configuration.Binder.dll)
CosmosDbClientSettings cosmosDbClientSettings = configuration.Get<CosmosDbClientSettings>();
string databaseName = cosmosDbClientSettings.CosmosDbDatabaseName;
string containerName = cosmosDbClientSettings.CosmosDbCollectionName;
string account = cosmosDbClientSettings.CosmosDbAccount;
string key = cosmosDbClientSettings.CosmosDbKey;
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
CosmosClient client = clientBuilder.WithConnectionModeDirect().Build();
CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerName);
//async event handler
EventHandler handler = null;
handler = async (sender, args) => {
initializeDatabase -= handler; //unsubscribe
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");
};
initializeDatabase += handler; //subscribe
initializeDatabase(null, EventArgs.Empty); //raise the event to initialize db
return cosmosDbService;
};
services.AddSingleton<ICosmosDbService>(factory);
return service;
}
}
Note the approach taken to get around the having to use async void
in a non-async event handler.
Reference Async/Await - Best Practices in Asynchronous Programming.
So now the Configure
can be properly invoked.
public class Startup : FunctionsStartup {
public override void Configure(IFunctionsHostBuilder builder) =>
builder.Services
.AddHttpClient()
.AddCosmosDbService();
}
Here's an example that I was able to whip up; it establishes a connection to Azure App Configuration for centralized configuration and feature management. One should be able to use all DI features, such as IConfiguration
and IOptions<T>
, just as they would in an ASP.NET Core controller.
NuGet Dependencies
Install-Package Microsoft.Azure.Functions.Extensions
Install-Package Microsoft.Extensions.Configuration.AzureAppConfiguration
Install-Package Microsoft.Extensions.Configuration.UserSecrets
Startup.cs
[assembly: FunctionsStartup(typeof(SomeApp.Startup))]
namespace SomeApp
{
public class Startup : FunctionsStartup
{
public IConfigurationRefresher ConfigurationRefresher { get; private set; }
public override void Configure(IFunctionsHostBuilder hostBuilder) {
if (ConfigurationRefresher is not null) {
hostBuilder.Services.AddSingleton(ConfigurationRefresher);
}
}
public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder configurationBuilder) {
var hostBuilderContext = configurationBuilder.GetContext();
var isDevelopment = ("Development" == hostBuilderContext.EnvironmentName);
if (isDevelopment) {
configurationBuilder.ConfigurationBuilder
.AddJsonFile(Path.Combine(hostBuilderContext.ApplicationRootPath, $"appsettings.{hostBuilderContext.EnvironmentName}.json"), optional: true, reloadOnChange: false)
.AddUserSecrets<Startup>(optional: true, reloadOnChange: false);
}
var configuration = configurationBuilder.ConfigurationBuilder.Build();
var applicationConfigurationEndpoint = configuration["APPLICATIONCONFIGURATION_ENDPOINT"];
if (!string.IsNullOrEmpty(applicationConfigurationEndpoint)) {
configurationBuilder.ConfigurationBuilder.AddAzureAppConfiguration(appConfigOptions => {
var azureCredential = new DefaultAzureCredential(includeInteractiveCredentials: false);
appConfigOptions
.Connect(new Uri(applicationConfigurationEndpoint), azureCredential)
.ConfigureKeyVault(keyVaultOptions => {
keyVaultOptions.SetCredential(azureCredential);
})
.ConfigureRefresh(refreshOptions => {
refreshOptions.Register(key: "Application:ConfigurationVersion", label: LabelFilter.Null, refreshAll: true);
refreshOptions.SetCacheExpiration(TimeSpan.FromMinutes(3));
});
ConfigurationRefresher = appConfigOptions.GetRefresher();
});
}
}
}
}
The newly released version 1.1.0 of Microsoft.Azure.Functions.Extensions allows you to do the following:
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var configuration = builder.GetContext().Configuration;
builder.Services.AddCosmosDbService(configuration);
}
}
Unfortunately it still does not support async configuration so you will still have to block waiting for the task to finish or use the trick described by @Nkosi