在 .NET Azure Functions 中使用依赖项注入

Azure Functions 支持依赖项注入(DI)软件设计模式,这是实现类与其依赖项之间的 控制反转(IoC) 的技术。

  • Azure Functions 中的依赖关系注入基于 .NET Core 依赖关系注入功能构建。 建议熟悉 .NET Core 依赖项注入 。 代替依赖项的方式与使用 Azure Functions 对消耗计划读取配置值的方式之间存在差异。

  • 对依赖项注入的支持从 Azure Functions 2.x 开始。

  • 依赖项注入模式因 C# 函数是进程内运行还是进程外运行而异。

重要

本文中的指南仅适用于使用运行时在进程内运行的 C# 类库函数。 此自定义依赖项注入模型不适用于 .NET 隔离函数,这允许在进程外运行 .NET 函数。 .NET 隔离的工作进程模型依赖于常规 ASP.NET 核心依赖项注入模式。 若要了解详细信息,请参阅 .NET 独立工作进程指南中的 依赖关系注入

先决条件

在使用依赖项注入之前,必须安装以下 NuGet 包:

注册服务

若要注册服务,请创建用于配置组件并将组件添加到 IFunctionsHostBuilder 实例的方法。 Azure Functions 主机创建一个实例 IFunctionsHostBuilder 并将其直接传递到方法中。

警告

对于在消耗计划或高级计划中运行的函数应用,对触发器中使用的配置值的修改可能会导致缩放错误。 类 FunctionsStartup 对这些属性所做的任何更改都会导致函数应用启动错误。

注入IConfiguration可能导致意外行为。 若要详细了解如何添加配置源,请参阅 自定义配置源

若要注册该方法,请添加 FunctionsStartup 程序集属性,该属性指定在启动期间使用的类型名称。

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

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

namespace MyNamespace;

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddHttpClient();

        builder.Services.AddSingleton<IMyService>((s) => {
            return new MyService();
        });

        builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
    }
}

此示例使用在启动时注册 HttpClient 所需的 Microsoft.Extensions.Http 包。

注意事项

在运行时处理启动类之前和之后会运行一系列注册步骤。 因此,请记住以下各项:

  • 启动类仅用于设置和注册。 避免在启动过程中使用在启动时注册的服务。 例如,请勿尝试在启动过程中注册的日志记录器中记录消息。 在注册过程的这一阶段,您的服务尚未可用。 Configure运行该方法后,Functions 运行时将继续注册其他依赖项,这可能会影响服务的运作方式。

  • 依赖项注入容器仅保留显式注册的类型。 唯一可用作可注入类型的服务是在 Configure 方法中设置的服务。 因此,像BindingContextExecutionContext这样的函数特定类型在设置过程中或作为可注入类型时都不可用。

  • 不支持配置 ASP.NET 身份验证。 Functions 主机会配置 ASP.NET 身份验证服务,以适当公开用于核心生命周期操作的 API。 自定义 Startup 类中的其他配置可以替代此配置,从而导致意外后果。 例如,调用 builder.Services.AddAuthentication() 可能会中断门户和主机之间的身份验证,导致出现“Azure Functions 运行时无法访问”等消息。

使用注入的依赖项

构造函数注入用于使依赖项在函数中可用。 使用构造函数注入要求不要对注入的服务或函数类使用静态类。

以下示例演示如何将 IMyServiceHttpClient 依赖项注入到一个由 HTTP 触发的函数中。

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using System.Net.Http;
using System.Threading.Tasks;

namespace MyNamespace;

public class MyHttpTrigger
{
    private readonly HttpClient _client;
    private readonly IMyService _service;

    public MyHttpTrigger(IHttpClientFactory httpClientFactory, IMyService service)
    {
        this._client = httpClientFactory.CreateClient();
        this._service = service;
    }

    [FunctionName("MyHttpTrigger")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        var response = await _client.GetAsync("https://microsoft.com");
        var message = _service.GetMessage();

        return new OkObjectResult("Response from function with injected dependencies.");
    }
}

此示例使用 Microsoft.Extensions.Http 包,该包用于在启动时注册HttpClient

服务生存期

Azure Functions 应用提供 与 ASP.NET 依赖项注入相同的服务生存期。 对于 Functions 应用程序,不同的服务生存期行为如下:

  • 瞬态:瞬态服务是在每次解析服务时创建的。
  • 限定范围:范围内服务生存期与函数执行生存期匹配。 每次执行函数后,都会创建设有范围的服务。 稍后在执行期间针对该服务的请求将重复使用现有服务实例。
  • 单一实例:单一实例服务生存期与主机生存期匹配,并在该实例上的函数执行中重复使用。 建议为连接和客户端使用单一实例生存期服务,例如 DocumentClientHttpClient 实例。

在 GitHub 上查看或下载 不同服务生存期的示例

日志记录服务

如果需要自己的日志记录提供程序,请将自定义类型注册为 ILoggerProvider(可通过 Microsoft.Extensions.Logging.Abstractions NuGet 包获取)的实例。

Application Insights 由 Azure Functions 自动添加。

警告

  • 不要将 AddApplicationInsightsTelemetry() 添加到服务集合中,因为该集合注册的服务会与环境提供的服务发生冲突。
  • 请勿注册您自己的 TelemetryConfigurationTelemetryClient,如果您正在使用内置的 Application Insights 功能。 如果需要配置自己的 TelemetryClient 实例,请通过插入的 TelemetryConfiguration 创建一个实例,如在 C# 函数中记录自定义遥测中所示。

ILogger<T> 和 ILoggerFactory

主机将 ILogger<T> 和服务 ILoggerFactory 注入到构造函数中。 但是,默认情况下,这些新的日志记录筛选器会从函数日志中筛选出来。 需要修改 host.json 文件以选择加入额外的筛选器和类别。

下面的示例演示如何添加包含向主机公开的日志的 ILogger<HttpTrigger>

namespace MyNamespace;

public class HttpTrigger
{
    private readonly ILogger<HttpTrigger> _log;

    public HttpTrigger(ILogger<HttpTrigger> log)
    {
        _log = log;
    }

    [FunctionName("HttpTrigger")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req)
    {
        _log.LogInformation("C# HTTP trigger function processed a request.");

        // ...
}

以下示例 host.json 文件添加日志筛选器。

{
    "version": "2.0",
    "logging": {
        "applicationInsights": {
            "samplingSettings": {
                "isEnabled": true,
                "excludedTypes": "Request"
            }
        },
        "logLevel": {
            "MyNamespace.HttpTrigger": "Information"
        }
    }
}

有关日志级别的详细信息,请参阅 “配置日志级别”。

函数应用提供的服务

函数宿主注册了许多服务。 以下服务可以安全地作为应用程序中的依赖项:

服务类型 生存期 DESCRIPTION
Microsoft.Extensions.Configuration.IConfiguration 单身 人士 运行时配置
Microsoft.Azure.WebJobs.Host.Executors.IHostIdProvider 单身 人士 负责提供主机实例的 ID

如果要依赖其他服务, 请创建问题并在 GitHub 上提出它们

替代主机服务

当前不支持替代主机提供的服务。 如果要替代某些服务, 请在 GitHub 上创建问题并提出它们

使用选项和设置

应用设置中定义的值在实例中IConfiguration可用,这样就可以在启动类中读取应用设置值。

可以从IConfiguration实例中提取值到自定义类型。 将应用设置值复制到自定义类型可使这些值可注入,从而轻松测试服务。 读入到配置实例中的设置必须是简单的键/值对。 对于在弹性高级计划中运行的函数,应用程序设置名称只能包含字母、数字(0-9)、句点(.)、冒号(:)和下划线(_)。 有关详细信息,请参阅 应用设置注意事项

请考虑以下类,该类包含一个名为与应用设置一致的属性:

public class MyOptions
{
    public string MyCustomSetting { get; set; }
}

以及一个 local.settings.json 文件,它可以按如下所示构造自定义设置:

{
  "IsEncrypted": false,
  "Values": {
    "MyOptions:MyCustomSetting": "Foobar"
  }
}

Startup.Configure方法内部,可以使用以下代码将IConfiguration实例中的值提取到自定义类型中:

builder.Services.AddOptions<MyOptions>()
    .Configure<IConfiguration>((settings, configuration) =>
    {
        configuration.GetSection("MyOptions").Bind(settings);
    });

调用 Bind 将具有匹配属性名称的值从配置复制到自定义实例中。 选项实例现已在 IoC 容器中提供,用于注入函数。

options 对象作为泛型 IOptions 接口的实例注入到函数中。 使用 Value 属性访问配置中找到的值。

using System;
using Microsoft.Extensions.Options;

public class HttpTrigger
{
    private readonly MyOptions _settings;

    public HttpTrigger(IOptions<MyOptions> options)
    {
        _settings = options.Value;
    }
}

有关详细信息,请参阅 ASP.NET Core 中的选项模式

使用 ASP.NET Core 用户机密

在本地开发应用时,ASP.NET Core 提供了一个 机密管理器工具 ,可用于将机密信息存储在项目根目录之外。 这使得机密意外提交到源代码管理的可能性更小。 Azure Functions Core Tools(版本 3.0.3233 或更高版本)会自动读取由 ASP.NET 核心机密管理器创建的机密。

若要将 .NET Azure Functions 项目配置为使用用户机密,请在项目根目录中运行以下命令。

dotnet user-secrets init

然后使用dotnet user-secrets set命令来创建或更新机密。

dotnet user-secrets set MySecret "my secret value"

若要访问函数应用代码中的用户机密值,请使用 IConfigurationIOptions

自定义配置源

若要指定其他配置源,请替代函数应用的 StartUp 类中的 ConfigureAppConfiguration 方法。

以下示例从基本和可选的特定于环境的应用设置文件中添加配置值。

using System.IO;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

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

namespace MyNamespace;

public class Startup : FunctionsStartup
{
    public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
    {
        FunctionsHostBuilderContext context = builder.GetContext();

        builder.ConfigurationBuilder
            .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: true, reloadOnChange: false)
            .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: true, reloadOnChange: false)
            .AddEnvironmentVariables();
    }
    
    public override void Configure(IFunctionsHostBuilder builder)
    {
    }
}

将配置提供程序添加到 IFunctionsConfigurationBuilderConfigurationBuilder 属性。 有关使用配置提供程序的详细信息,请参阅 ASP.NET Core 中的配置

FunctionsHostBuilderContext 是从 IFunctionsConfigurationBuilder.GetContext() 中获取的。 使用此上下文检索当前环境名称,并解析函数应用文件夹中配置文件的位置。

默认情况下,配置文件(例如 appsettings.json 不会自动复制到函数应用的输出文件夹)。 请更新你的.csproj文件以匹配以下示例,以确保文件能够被复制。

<None Update="appsettings.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>      
</None>
<None Update="appsettings.Development.json">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>

后续步骤

有关详细信息,请参阅以下资源: