Normally in a .NET Core project I would create a \'boostrap\' class to configure my service along with the DI registration commands. This is usually an extension method of
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();
});
}
}
}
}
I´m using .net core 3.1
[assembly: FunctionsStartup(typeof(Startup))]
namespace xxxxx.Functions.Base
{
[ExcludeFromCodeCoverage]
public class Startup : FunctionsStartup
{
private static IConfiguration _configuration = null;
public override void Configure(IFunctionsHostBuilder builder)
{
var serviceProvider = builder.Services.BuildServiceProvider();
_configuration = serviceProvider.GetRequiredService<IConfiguration>();
*** Now you can use _configuration["KEY"] in Startup.cs ***
}
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