Azure-docs: Add guidance on using configuration in Startup

Created on 8 Jun 2019  Â·  13Comments  Â·  Source: MicrosoftDocs/azure-docs

Would be good to understand the best practice for using configuration in the Startup (FunctionsStartup) class, seems confusing which options work locally vs in Azure. I've tried:

  • Using a ConfigurationBuilder but this doesn't work with complex objects in local.settings.json (I've yet to test this in Azure)
  • Getting the IConfiguration instance from the service provider using BuildServiceProvider - this feels wrong but also didn't pick up local settings in the expected way
  • Using Environment.GetEnvironmentVariable, this works for the Values section and ConnectionStrings in local.settings.json but when used in Azure only reads app settings meaning connection strings need moving/duplicating as app settings

Would be good to know the preferred approach and if this is still a work in progress, thanks.


Document Details

⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

Pri1 assigned-to-author azure-functionsvc doc-enhancement triaged

Most helpful comment

Hi,

Just thought to give my input, on how I did this (also supporting complex configs):

My local.settings.json:

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobsDashboard": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",    
    "OriginationConfigs:eventStore:eventStoreEndpoint": "someendpoint",
    "OriginationConfigs:eventGrid:companyTopic": "http://localhost:5000/eventgrid",
    "OriginationConfigs:eventGrid:privateKey": "privatekey",
    "OriginationConfigs:azureSearch:searchServiceName": "searchServiceName",
    "OriginationConfigs:azureSearch:adminApiKey": "adminapikey",
    "OriginationConfigs:OriginationFrondEndUrl": "https://fontend",
    "OriginationConfigs:LookupServiceApiUrl": "https://lookup",
    "OriginationConfigs:CreditServiceApiUrl": "https://lookup",
    "OriginationConfigs:OrganisationServiceApiURL": "https://lookup",
    "OriginationConfigs:OriginationWriteApiBaseUrl": "http://localhost:58596/",
    "OriginationConfigs:OriginationFrontEndUrl": "http://localhost:58596/"
  }
}

Startup.cs of the function:

public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
                var config = builder.Services.BuildServiceProvider().GetRequiredService(typeof(IConfiguration)) as IConfiguration;
                var originationConfiguration = new OriginationConfiguration();

                config.GetSection("OriginationConfigs").Bind(originationConfiguration);

                //custom extension method to add my business services
                builder.Services.AddOriginationServices(originationConfiguration);          
        }
    }

And the OriginationConfiguration.cs respects the "json" structure of the local.settings.json:

public class OriginationConfiguration
    {
        public EventStoreConfig EventStore { get; set; }

        public EventGridConfig EventGrid { get; set; }

        public AzureSearchConfig AzureSearch { get; set; }

        public string OriginationWriteApiBaseUrl { get; set; }

        public string OriginationFrontEndUrl { get; set; }

        public string CreditServiceApiUrl { get; set; }

        public string OrganisationServiceApiURL { get; set; }

        public string LookupServiceApiUrl { get; set; }
    }

You can't have a complex structure in the local.settings.json... but what you do is you respect the convention of delimiting nodes with : , just like OriginationConfigs:eventStore:eventStoreEndpoint .. this will bind to the instance of OriginationConfiguration.EventStore.EventStoreEndpoint .

Works well on local as well as in Azure.

The only thing that I don't like is having to build the ServiceProvider in the StartupClass... adding the IConfiguration to the function's constructor works fine, but that's not where you want to build your service collection (assuming your service collection needs some configs), so I would like to see a more elegant approach and/or how to get the IConfiguration in the Startup.

Vlad

All 13 comments

Thanks for the feedback! We are currently investigating and will update you shortly.

I would be very interested in the outcome of this. I'm trying to use the Options pattern described in other Microsoft documentation here: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2 but am not sure what the advised way of accessing the IConfiguration object.

I have previously had issues with creating my own configuration object, as one is registered automatically for use in my functions, but I also cannot inject into the Startup class.

@benbelow good point, I did also try the options pattern and you can do it but only with an Action where you set each property using GetEnvironmentVariable from individual settings in the Values section (if local) or app settings in Azure. This works but it’s clunky.

There is no published guidance around this.

My Host.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet"
  },

  "SubscriptionId": "XXXXXXXXXXXXXXXXXX",

I am currently doing

 public class Startup : FunctionsStartup
    {
        IConfiguration _configuration;

        public override void Configure(IFunctionsHostBuilder builder)
        {
            _configuration = builder.Services
                .Where(s => s.ServiceType == typeof(IConfiguration)).First()
                .ImplementationInstance as IConfiguration;

        }

    }

and then

var SubscriptionId = _configuration["AzureFunctionsJobHost:SubscriptionId"];

It works, but I don't know if this is the optimal way.
Am curious to know as well.

Looks like we need to wait until #4464 is addressed

Hi,

Just thought to give my input, on how I did this (also supporting complex configs):

My local.settings.json:

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "AzureWebJobsDashboard": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",    
    "OriginationConfigs:eventStore:eventStoreEndpoint": "someendpoint",
    "OriginationConfigs:eventGrid:companyTopic": "http://localhost:5000/eventgrid",
    "OriginationConfigs:eventGrid:privateKey": "privatekey",
    "OriginationConfigs:azureSearch:searchServiceName": "searchServiceName",
    "OriginationConfigs:azureSearch:adminApiKey": "adminapikey",
    "OriginationConfigs:OriginationFrondEndUrl": "https://fontend",
    "OriginationConfigs:LookupServiceApiUrl": "https://lookup",
    "OriginationConfigs:CreditServiceApiUrl": "https://lookup",
    "OriginationConfigs:OrganisationServiceApiURL": "https://lookup",
    "OriginationConfigs:OriginationWriteApiBaseUrl": "http://localhost:58596/",
    "OriginationConfigs:OriginationFrontEndUrl": "http://localhost:58596/"
  }
}

Startup.cs of the function:

public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
                var config = builder.Services.BuildServiceProvider().GetRequiredService(typeof(IConfiguration)) as IConfiguration;
                var originationConfiguration = new OriginationConfiguration();

                config.GetSection("OriginationConfigs").Bind(originationConfiguration);

                //custom extension method to add my business services
                builder.Services.AddOriginationServices(originationConfiguration);          
        }
    }

And the OriginationConfiguration.cs respects the "json" structure of the local.settings.json:

public class OriginationConfiguration
    {
        public EventStoreConfig EventStore { get; set; }

        public EventGridConfig EventGrid { get; set; }

        public AzureSearchConfig AzureSearch { get; set; }

        public string OriginationWriteApiBaseUrl { get; set; }

        public string OriginationFrontEndUrl { get; set; }

        public string CreditServiceApiUrl { get; set; }

        public string OrganisationServiceApiURL { get; set; }

        public string LookupServiceApiUrl { get; set; }
    }

You can't have a complex structure in the local.settings.json... but what you do is you respect the convention of delimiting nodes with : , just like OriginationConfigs:eventStore:eventStoreEndpoint .. this will bind to the instance of OriginationConfiguration.EventStore.EventStoreEndpoint .

Works well on local as well as in Azure.

The only thing that I don't like is having to build the ServiceProvider in the StartupClass... adding the IConfiguration to the function's constructor works fine, but that's not where you want to build your service collection (assuming your service collection needs some configs), so I would like to see a more elegant approach and/or how to get the IConfiguration in the Startup.

Vlad

@fabiocav - can you please comment on what's possible here?

I'm currently using this code:
``` c#
public override void Configure(IFunctionsHostBuilder builder)
{
var configBuilder = new ConfigurationBuilder();
configBuilder.AddEnvironmentVariables();

var configuration = configBuilder.Build();

builder.Services.Configure<FunctionAppOptions>(configuration.GetSection("FunctionAppOptions"));

}
```

@StefH Could you please expand your example on how to access the configuration in a function? I'm somewhat at loss here.

@HSBallina I created a NuGet package that should help. Check it out.

@HSBallina My full example project can be found at https://github.com/StefH/SmartContractFunction/tree/master/src/SmartContractAzureFunctionApp

See the Startup.cs file for details.

@MisinformedDNA Your solution uses the same principles as I use in my sample project.

Configuration support is a feature that the product team is investigating. Since this is more of a product issue than a doc issue, I am going to #please-close this thread for now.

To request specific features, please feel free to open an issue propose them in the azure-functions-host repo.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JeffLoo-ong picture JeffLoo-ong  Â·  3Comments

DeepPuddles picture DeepPuddles  Â·  3Comments

Ponant picture Ponant  Â·  3Comments

spottedmahn picture spottedmahn  Â·  3Comments

jebeld17 picture jebeld17  Â·  3Comments