Last active
April 5, 2025 17:10
-
-
Save cajuncoding/f8a07ad4968d4f69184dd39af9c4ee92 to your computer and use it in GitHub Desktop.
Autofac Integration for Azure Functions v4 In-Process Model
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Reflection; | |
using Autofac; | |
using Autofac.Extensions.DependencyInjection; | |
using Module = Autofac.Module; | |
using Microsoft.Azure.Functions.Extensions.DependencyInjection; | |
using Microsoft.Azure.WebJobs; | |
using Microsoft.Azure.WebJobs.Host; | |
using Microsoft.Azure.WebJobs.Host.Executors; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.DependencyInjection.Extensions; | |
using Microsoft.Extensions.Logging; | |
namespace CajunCoding.Autofac | |
{ | |
/// <summary> | |
/// BBernard / CajunCoding (MIT License) | |
/// Classes & Extension methods for easily integrating Autofac within AzureFunctions v4 in-process projects. | |
/// It is enhanced but heavily based on the original solution in the official Autofac documentation. | |
/// The main benefit being that this now fully encapsulates it in a single class with an IFunctionsHost extension method | |
/// that takes care of all the work so that you can simply configure the container via the lambda as needed. | |
/// </summary> | |
public static class AutofacAzureFunctionsCustomExtensions | |
{ | |
/// <summary> | |
/// Enables Autofac handling of DI for Azure Functions v4 in-process model! | |
/// This method will automatically import anything already registered from the default Azure Functions DI container | |
/// and then allow further configuration of the Autofac container via the buildContainerAction lambda parameter. | |
/// </summary> | |
/// <typeparam name="TFunctionsAssembly">Any Type that exists within the Assembly that contains the Azure Function classes (e.g. classes that contain methods with [FunctionName()] attribute(s).)</typeparam> | |
/// <param name="functionsHostBuilder">Extension method for IFunctionsHostBuilder</param> | |
/// <param name="buildContainerAction">Action to configure the Autofac container.</param> | |
/// <returns></returns> | |
public static IContainer UseAutofacAzureFunctionsDependencyInjection<TFunctionsAssembly>( | |
this IFunctionsHostBuilder functionsHostBuilder, | |
Action<ContainerBuilder> buildContainerAction = null | |
) | |
{ | |
var services = functionsHostBuilder.Services; | |
var containerBuilder = new ContainerBuilder(); | |
containerBuilder.Populate(services); | |
containerBuilder.RegisterModule<LoggerModule>(); | |
// This is a convenient way to register all your function classes at once containerBuilder | |
// by locating & registering all classes with the [FunctionName] attribute on any of it's Methods... | |
containerBuilder.RegisterAssemblyTypes(typeof(TFunctionsAssembly).Assembly) | |
.Where(t => t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static).Any( | |
m => m.GetCustomAttribute<FunctionNameAttribute>() != null | |
)); | |
//Allow configuration of the Container by calling code... | |
buildContainerAction?.Invoke(containerBuilder); | |
//Build and initialize Autofac with Azure Functions framework.... | |
var container = containerBuilder.Build(); | |
services | |
.AddSingleton(container) | |
.AddScoped<LifetimeScopeWrapper>() | |
.Replace(ServiceDescriptor.Singleton(typeof(IJobActivator), AutofacAzureFunctionsJobActivator.CachedType)) | |
.Replace(ServiceDescriptor.Singleton(typeof(IJobActivatorEx), AutofacAzureFunctionsJobActivator.CachedType)); | |
return container; | |
} | |
} | |
/// <summary> | |
/// This Class is taken directly from the official Autofac Docs and slightly simplified with updated C# syntax: | |
/// https://autofac.readthedocs.io/en/latest/integration/azurefunctions.html# | |
/// | |
/// NOTE: When we move to Azure Functions Isolated Process model we may no longer need this at all! | |
/// | |
/// </summary> | |
public class AutofacAzureFunctionsJobActivator : IJobActivatorEx | |
{ | |
public static Type CachedType { get; } = typeof(AutofacAzureFunctionsJobActivator); | |
// In practice, this method will not get called. We cannot safely resolve the type T here because we | |
// don't have access to an ILifetimeScope, so it's better to just throw. | |
public T CreateInstance<T>() => throw new NotSupportedException(); | |
public T CreateInstance<T>(IFunctionInstanceEx functionInstance) where T : notnull | |
{ | |
var functionServices = functionInstance.InstanceServices; | |
var lifetimeScope = functionServices.GetRequiredService<LifetimeScopeWrapper>().Scope; | |
//NOTE: Taken directly from Autofac Documentation on Azure Functions integration... | |
// This is necessary because some dependencies of ILoggerFactory are registered after FunctionsStartup. | |
var loggerFactory = functionServices.GetRequiredService<ILoggerFactory>(); | |
lifetimeScope.Resolve<ILoggerFactory>(new NamedParameter(LoggerModule.LoggerFactoryParam, loggerFactory)); | |
lifetimeScope.Resolve<ILogger>(new NamedParameter(LoggerModule.FunctionNameParam, functionInstance.FunctionDescriptor.LogName)); | |
return lifetimeScope.Resolve<T>(); | |
} | |
} | |
/// <summary> | |
/// Lifetime Scope Wrapper allows the proper handling of Autofac Scope by delegating to Microsoft's default DI Scope Handling via AddScoped<LifetimeScopeWrapper>(); | |
/// </summary> | |
public sealed class LifetimeScopeWrapper : IDisposable | |
{ | |
public ILifetimeScope Scope { get; } | |
public LifetimeScopeWrapper(IContainer container) | |
{ | |
Scope = container.BeginLifetimeScope(); | |
} | |
public void Dispose() => Scope?.Dispose(); | |
} | |
/// <summary> | |
/// Azure functions in-process model has non-intuitive handling of logging and (non-typed) ILogger is not inherently available as would be expected. | |
/// This LoggerModule ensures that the ILoggerFactory & non-typed ILogger are initialized and available as expected by Azure Functions; | |
/// this is critical for log streaming support to the console and Azure Portal. | |
/// </summary> | |
public 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(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With this class and extension method it's much easier to quickly add Autofac support in your Azure Functions v4 in-process projects. This is heavily inspired by the officially documented solution, but with a few improvements such as:
Implementation is now as easy as: