How to inject or use IConfiguration in Azure Function V3 with Dependency Injection when configuring a service

前端 未结 4 1879
挽巷
挽巷 2021-02-02 12:46

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

相关标签:
4条回答
  • 2021-02-02 13:02

    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();
    }
    
    0 讨论(0)
  • 2021-02-02 13:02

    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();
                    });
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-02 13:10

    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 ***
            }
    
    0 讨论(0)
  • 2021-02-02 13:15

    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

    0 讨论(0)
提交回复
热议问题