Using Dependency Injection and IOptions in Azure Functions

So, I created this Azure Function and because I have a lot of parameters that I would like to be able to use in dependency injection I did not want to go with the familiar way like this:

var firstName = Environment.GetEnvironmentVariable("FirstName");

Instead I want to use the Options pattern as I would do when creating a “normal” appsettings file.

However, Azure Function configurations only feature key-value pairs for configuration and do not support JSON configuration files. Luckily there is a way around this using a little peculiar syntax.

Writing the code

First, we create our options class:

public class ParentOptions
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public ChildOptions ChildOptions { get; set; }
}

public class ChildOptions
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public List<string> FavouriteFoods { get; set; }
}

After creating this class we configure dependency injection so we can use it in any service later on. A neat little trick I used here is to add the ParentOptions class itself as a service. This way I don’t need to use IOptions<ParentOptions> in every constructor when I want to use these settings.

public static void AddOptions(this IServiceCollection services)
        {
            services.AddOptions<ParentOptions>()
                .Configure<IConfiguration>((settings, configuration) =>
                {
                    configuration.GetSection("ParentOptions").Bind(settings);
                });

            services.Add(new ServiceDescriptor(typeof(ParentOptions), provider =>
            {
                var options = provider.GetRequiredService<IOptions<ParentOptions>>();
                return options.Value;
            }, ServiceLifetime.Singleton));
        }

Next, use the settings. For simplicity I have just injected them into the constructor of my functions class:

public class Function1
    {
        private readonly ILogger _logger;
        private readonly ParentOptions _settings;

        public Function1(ILoggerFactory loggerFactory, ParentOptions settings)
        {
            _logger = loggerFactory.CreateLogger<Function1>();
            _settings = settings;
        }

        [Function("Function1")]
        public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            var response = req.CreateResponse(HttpStatusCode.OK);

            //Let's see if we have our config right...
            response.WriteAsJsonAsync(_settings);

            return response;
        }
    }

Creating the configuration file

Lastly, I need these values to be available in my config file. This is where most of the rub is, because this syntax can hardly be guessed if you did not come across it before.

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "ParentOptions:FirstName": "T.F.",
    "ParentOptions:LastName": "Menhart",
    "ParentOptions:ChildOptions__FirstName": "Jos",
    "ParentOptions:ChildOptions__LastName": "Menhart",
    "ParentOptions:ChildOptions__Age": 40,
    "ParentOptions:ChildOptions__FavouriteFoods:[0]": "French fries",
    "ParentOptions:ChildOptions__FavouriteFoods:[1]": "Sushi"
  }
}

Explaining the config file

  • The C# “GetSection” finds the ParentOptions, before the colon. This is the parent class.
  • Any member of that section is accessed by the name after the colon
  • Any member of the child class is accessed by inserting a double underscore after the child class name.
  • For fun I have also added an array configuration. I did not need it in my original project but I wanted to find out if that would work 🙂

Testing

Finally, let’s run the finished project, and see if the settings are picked up correctly:

Great, it worked!

Azure says no to :…

One more thing: after deploying a function using the Root:ChildElement syntax to Azure I received an error deploying my Bicep file about this syntax. I checked in the portal and indeed saw the message: “App setting names can only contain letters, numbers (0-9), periods (“.”), and underscores (“_”)

Image: App setting names can only contain letters, numbers (0-9), periods ("."), and underscores ("_")

Btw, you can find the working example project on my GitHub page.