项目
版本

Autofac Azure Functions 集成

Azure Functions 支持使用 Microsoft 的依赖注入框架进行依赖注入,但你可以通过添加一些引导代码使其与 Autofac 一起工作。

我们建议阅读官方 Microsoft 文档 《Azure Functions 中的依赖注入概述》 ,以了解 Azure Functions 中依赖注入的基本概念。

步骤概览

  1. 从 NuGet 安装 Autofac、Autofac.Extensions.DependencyInjectionMicrosoft.Azure.Functions.Extensions
  2. 添加一个基于 Autofac 的工作器激活器,用于创建你的函数类实例。
  3. 创建一个 Startup 类,在其中注册你的组件,并替换默认的工作器激活器。

Autofac 工作器激活器

工作器激活器负责实例化包含你的函数的类。在项目中添加以下代码——它是一个基于 Autofac 生命周期范围的工作器激活器,用于从生命周期范围中获取适当的类。接下来我们将实现 LifetimeScopeWrapperLoggerModule

internal class AutofacJobActivator : IJobActivatorEx
{
    public T CreateInstance<T>()
    {
        // 在实际应用中,这个方法不会被调用。因为无法安全地在此处解析T(因为我们没有访问ILifetimeScope),所以最好直接抛出异常。
        throw new NotSupportedException();
    }

    public T CreateInstance<T>(IFunctionInstanceEx functionInstance)
        where T : notnull
    {
        var lifetimeScope = functionInstance.InstanceServices
            .GetRequiredService<LifetimeScopeWrapper>()
            .Scope;

        // 这是必要的,因为ILoggerFactory的一些依赖项是在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>();
    }
}

接下来,实现 LifetimeScopeWrapper 。这个类从 IServiceCollection 中解析,允许我们在函数执行完毕后释放 Autofac 生命周期范围。

internal sealed class LifetimeScopeWrapper : IDisposable
{
    public ILifetimeScope Scope { get; }

    public LifetimeScopeWrapper(IContainer container)
    {
        Scope = container.BeginLifetimeScope();
    }

    public void Dispose()
    {
        Scope.Dispose();
    }
}

为了能够解析 ILogger ,我们需要特殊处理,因为某些日志工厂直到 Startup 类运行后才会初始化。我们可以通过添加以下代码来解决这个问题。

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();
    }
}

即使你没有直接使用 ILogger ,也应该将 LoggerModule 包含在项目中,因为 Microsoft 的许多 NuGet 包都引用了此接口。

Startup

最后,添加一个 Startup 类将所有内容连接起来。这个类在概念上类似于 ASP.NET Core 项目的 Startup 类。

FunctionsStartup 基类由 Microsoft.Azure.Functions.Extensions NuGet 包提供。

[assembly: FunctionsStartup(typeof(MyFunctionApp.Startup))]

namespace MyFunctionApp;

internal class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        // 使用IServiceCollection.Add扩展方法按需添加功能,例如:
        builder.Services.AddDataProtection();

        builder.Services.AddSingleton(GetContainer(builder.Services));

        // 重要:使用AddScoped,以便我们的Autofac生命周期范围在函数执行完毕时被释放
        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>();

        // 这是一种方便的方法,一次注册所有你的函数类
        containerBuilder.RegisterAssemblyTypes(typeof(Startup).Assembly)
            .InNamespaceOf<Function1>();

        // TODO:像正常一样使用ContainerBuilder注册其他依赖项

        return containerBuilder.Build();
    }
}

就这样!现在你的函数类将从 Autofac 中解析。

示例函数

这是一个使用依赖注入服务的 HTTP 触发函数示例。请注意,类和 Run 方法不是静态的。

public class Function1
{
    private readonly IRandomNumberService _randomNumberService;

    public Function1(IRandomNumberService randomNumberService)
    {
        _randomNumberService = randomNumberService;
    }

    // 通过在浏览器中访问http://localhost:7071/api/Function1来调用此方法
    [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}.");
    }
}

致谢

本指南受到了社区 NuGet 包 Autofac.Extensions.DependencyInjection.AzureFunctions 的启发。如果你更喜欢使用 NuGet 包而不是这里介绍的自定义方法,可以尝试一下 Autofac.Extensions.DependencyInjection.AzureFunctions

在本文档中