Azure Functions
Azure Functions supports dependency injection with the Microsoft dependency injection framework out of the box, but you can make it work with Autofac with a bit of bootstrapping code.
We recommend reading the official Microsoft documentation for an overview of dependency injection in the context of Azure Functions.
Overview of Steps
Install Autofac,
Autofac.Extensions.DependencyInjection, andMicrosoft.Azure.Functions.Extensionsfrom NuGet.Add an Autofac-based job activator to create instances of your function classes.
Create a
Startupclass where you register your components and replace the default job activator.
Autofac Job Activator
A job activator is responsible for instantiating the classes that hold your
functions. Add the following code to your project — it’s a job activator that
resolves the appropriate class from an Autofac lifetime scope. We’ll implement
LifetimeScopeWrapper and LoggerModule in the next steps.
internal class AutofacJobActivator : IJobActivatorEx
{
public T CreateInstance<T>()
{
// In practice, this method will not get called. We cannot safely resolve T here
// because we don't have access to an ILifetimeScope, so it's better to just
// throw.
throw new NotSupportedException();
}
public T CreateInstance<T>(IFunctionInstanceEx functionInstance)
where T : notnull
{
var lifetimeScope = functionInstance.InstanceServices
.GetRequiredService<LifetimeScopeWrapper>()
.Scope;
// This is necessary because some dependencies of ILoggerFactory are registered
// after FunctionsStartup.
var loggerFactory = functionInstance.InstanceServices.GetRequiredService<ILoggerFactory>();
lifetimeScope.Resolve<ILoggerFactory>(
new NamedParameter(LoggerModule.LoggerFactoryParam, loggerFactory)
);
lifetimeScope.Resolve<ILogger>(
new NamedParameter(LoggerModule.FunctionNameParam, functionInstance.FunctionDescriptor.LogName)
);
return lifetimeScope.Resolve<T>();
}
}
Next, implement LifetimeScopeWrapper. This class is resolved from the
IServiceCollection and allows us to dispose the Autofac lifetime scope after
the function has completed.
internal sealed class LifetimeScopeWrapper : IDisposable
{
public ILifetimeScope Scope { get; }
public LifetimeScopeWrapper(IContainer container)
{
Scope = container.BeginLifetimeScope();
}
public void Dispose()
{
Scope.Dispose();
}
}
Special logic is needed for us to be able to resolve ILogger because certain
logger factories are not initialized until after the Startup class has run.
We can work around this by adding the following code.
internal class LoggerModule : Module
{
public const string LoggerFactoryParam = "loggerFactory";
public const string FunctionNameParam = "functionName";
protected override void Load(ContainerBuilder builder)
{
builder.Register((ctx, p) => p.Named<ILoggerFactory>(LoggerFactoryParam))
.SingleInstance();
builder.Register((ctx, p) =>
{
var factory = ctx.Resolve<ILoggerFactory>();
var functionName = p.Named<string>(FunctionNameParam);
return factory.CreateLogger(Microsoft.Azure.WebJobs.Logging.LogCategories.CreateFunctionUserCategory(functionName));
})
.InstancePerLifetimeScope();
}
}
LoggerModule should be included in your project even if you don’t use
ILogger directly, since this interface is referenced by many of Microsoft’s
NuGet packages.
Startup Class
Finally, add a Startup class to tie everything together. This class is
conceptually very similar to the Startup class in ASP.NET Core projects.
The FunctionsStartup base class is provided by the
Microsoft.Azure.Functions.Extensions NuGet package.
[assembly: FunctionsStartup(typeof(MyFunctionApp.Startup))]
namespace MyFunctionApp;
internal class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
// Use IServiceCollection.Add extension method to add features as needed, e.g.
builder.Services.AddDataProtection();
builder.Services.AddSingleton(GetContainer(builder.Services));
// Important: Use AddScoped so our Autofac lifetime scope gets disposed
// when the function finishes executing
builder.Services.AddScoped<LifetimeScopeWrapper>();
builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IJobActivator), typeof(AutofacJobActivator)));
builder.Services.Replace(ServiceDescriptor.Singleton(typeof(IJobActivatorEx), typeof(AutofacJobActivator)));
}
private static IContainer GetContainer(IServiceCollection serviceCollection)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(serviceCollection);
containerBuilder.RegisterModule<LoggerModule>();
// This is a convenient way to register all your function classes at once
containerBuilder.RegisterAssemblyTypes(typeof(Startup).Assembly)
.InNamespaceOf<Function1>();
// TODO: Register other dependencies with the ContainerBuilder like normal
return containerBuilder.Build();
}
}
And that’s it! Your function classes will now be resolved from Autofac.
Example Function
Here’s an example of an HTTP-triggered function that uses a service from
dependency injection. Notice that the class and Run method are not static.
public class Function1
{
private readonly IRandomNumberService _randomNumberService;
public Function1(IRandomNumberService randomNumberService)
{
_randomNumberService = randomNumberService;
}
// Call this by going to http://localhost:7071/api/Function1 in your web browser
[FunctionName("Function1")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]
HttpRequest request
)
{
var number = _randomNumberService.GetDouble();
return new OkObjectResult($"Your random number is {number}.");
}
}
Acknowledgements
This guide was inspired by
Autofac.Extensions.DependencyInjection.AzureFunctions, a community NuGet
package. Give Autofac.Extensions.DependencyInjection.AzureFunctions a try if
you would prefer a NuGet package over the DIY approach presented here.