Dependency injection for Azure Function v2

Since Azure Function v2 still does not yet has support for dependency injection and this is still a hot topic, I decided to upgrade my soluton for this to Azure Function v2 and provide a nuget package!

Welcome Willezone.Azure.WebJobs.Extensions.DependencyInjection!

Although there is still not support for dependency injection, we can actually provide this as an extension. Luckily Azure Functions v2 comes with a new ways to integrate and register extensions, which also makes it easier to provide this extension! The extension is available as a nuget package. Out of the box Microsoft.Extensions.DependencyInjection is used for dependency injection, but it is possible to use any IoC container that implements the IServiceProvider interface (see https://github.com/aspnet/DependencyInjection for more IoC containers).

How to configure

Once the package is added to the function project, a WebJobsStartup class is needed to register and configure the dependency injection bindings.

This is an example WebJobsStartup class

using ExampleFunction;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Willezone.Azure.WebJobs.Extensions.DependencyInjection;

[assembly: WebJobsStartup(typeof(Startup))]
namespace ExampleFunction
{
    internal class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder) =>
            builder.AddDependencyInjection(ConfigureServices);

        private void ConfigureServices(IServiceCollection services)
        {
            services.AddTransient<ITransientGreeter, Greeter>();
            services.AddScoped<IScopedGreeter, Greeter>();
            services.AddSingleton<ISingletonGreeter, Greeter>();
        }
    }

}

The nuget package contains two extension methods to register the dependency injection extensions.

AddDependencyInjection(this IWebJobsBuilder builder, Action<IServiceCollection> configureServices)

This configures the extension using the Microsoft.Extensions.DependencyInjection container. Services can be registered in the configureServices action.

AddDependencyInjection<TServiceProviderBuilder>(this IWebJobsBuilder builder) 
    where TServiceProviderBuilder : IServiceProviderBuilder

This configures the extension to use what ever IoC container is returned from the Build method of the IServiceProviderBuilder implementation. It also gives access to other components, e.g. the configuration.

Example that uses Autofac

public class AutofacServiceProviderBuilder : IServiceProviderBuilder
{
    private readonly IConfiguration _configuration;

    public AutofacServiceProviderBuilder(IConfiguration configuration) => 
        _configuration = configuration;

    public IServiceProvider Build()
    {
        // Get a setting from the configuration.
        Debug.WriteLine(_configuration["Setting"]); 

        var services = new ServiceCollection();
        services.AddTransient<ITransientGreeter, Greeter>();
        services.AddScoped<IScopedGreeter, Greeter>();
        services.AddSingleton<ISingletonGreeter, Greeter>();

        var builder = new ContainerBuilder();
        builder.Populate(services); // Populate is needed to have support for scopes.

        return new AutofacServiceProvider(builder.Build());
    }
}

Using the extension

Once the extension is registered and configured, dependencies can be injected using the Inject attribute on a function.

Example

[FunctionName("Greeter")]
public static IActionResult Run(
    [HttpTrigger(AuthorizationLevel.Function, "get")]HttpRequest req,
    [Inject]ITransientGreeter transientGreeter,
    [Inject]IScopedGreeter scopedGreeter,
    [Inject]ISingletonGreeter singletonGreeter,
    ILogger logger)
{
    logger.LogInformation("C# HTTP trigger function processed a request.");

    var result = String.Join(Environment.NewLine, new[] {
        $"Transient: {transientGreeter.Greet()}",
        $"Scoped: {scopedGreeter.Greet()}",
        $"Singleton: {singletonGreeter.Greet()}",
    });
    return new OkObjectResult(result);
}

Source

The source for this can be found on Github. Feel free to fork it or open issues!