Usar injeção de dependência no .NET do Azure Functions

O Azure Functions dá suporte ao padrão de design de software injeção de dependência (DI), que é uma técnica para executar a Inversão de Controle (IoC) entre classes e suas dependências.

  • A injeção de dependência no Azure Functions é desenvolvida usando como base os recursos de injeção de dependência do .NET Core. Recomendamos conhecer bem a injeção de dependência do .NET Core. Há diferenças em como substituir dependências e como os valores de configuração são lidos com o Azure Functions no plano de consumo.

  • O suporte para injeção de dependência começa com Azure Functions 2.x.

  • Os padrões de injeção de dependência diferem dependendo se as funções C# são executadas em processo ou fora do processo.

Importante

As diretrizes neste artigo se aplicam somente às funções de biblioteca de classes C#, que são executadas em processo com o runtime. Esse modelo de injeção de dependência personalizado não se aplica a funções isoladas do .NET, o que permite executar funções do .NET 5.0 fora do processo. O modelo de processo de trabalho isolado do .NET depende de padrões de injeção de dependência do ASP.NET Core regulares. Para saber mais, confira Injeção de dependência no guia de processo de trabalho isolado do .NET.

Pré-requisitos

Antes de poder usar a injeção de dependência, você precisa instalar os seguintes pacotes NuGet:

Serviços de registro

Para registrar serviços, crie um método para configurar e adicionar componentes a uma instância IFunctionsHostBuilder. O host do Azure Functions cria uma instância IFunctionsHostBuilder e a transmite diretamente para o seu método.

Aviso

Para aplicativos de funções que executam nos planos Consumption ou Premium, as modificações nos valores de configuração usados em gatilhos podem causar erros de colocação em escala. Qualquer alteração nessas propriedades pela classe FunctionsStartup resulta em um erro de inicialização do aplicativo de funções.

A injeção de IConfiguration pode levar a um comportamento inesperado. Para saber mais sobre como adicionar fontes de configuração, consulte Personalizando fontes de configuração.

Para registrar o método, adicione o atributo de assembly FunctionsStartup que especifica o nome de tipo usado durante a inicialização.

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

Este exemplo usa o pacote Microsoft.Extensions.Http necessário para registrar um HttpClient na inicialização.

Advertências

Uma série de etapas de registro executada antes e depois que o runtime processa a classe de inicialização. Assim, tenha em mente os seguintes itens:

  • A classe de inicialização destina-se apenas à instalação e ao registro. Evite usar serviços registrados na inicialização durante o processo de inicialização. Por exemplo, não tente registrar uma mensagem em um agente que está sendo registrado durante a inicialização. Nesse ponto do processo de registro, os serviços ainda não estão disponíveis para uso. Depois que o método Configure é executado, o runtime do Functions continua registrando outras dependências, o que pode afetar a forma como seus serviços operam.

  • O contêiner de injeção de dependência só mantém tipos explicitamente registrados. Os únicos serviços disponíveis como tipos injetáveis são os que são configurados no método Configure. Como resultado, tipos específicos do Functions, como BindingContext e ExecutionContext, não estão disponíveis durante a instalação ou como tipos injetáveis.

  • Não há suporte para configurar a autenticação ASP.NET. O host do Functions configura ASP.NET serviços de autenticação para expor adequadamente AS APIs para operações de ciclo de vida principais. Outras configurações em uma classe de Startup personalizada podem substituir essa configuração, causando consequências não intencionais. Por exemplo, chamar builder.Services.AddAuthentication() pode interromper a autenticação entre o portal e o host, levando a mensagens como o runtime do Azure Functions é inacessível.

Usar dependências injetadas

A injeção de construtor é usada para disponibilizar suas dependências em uma função. O uso da injeção de construtor requer que você não use classes estáticas para serviços injetados ou para suas classes de função.

O exemplo a seguir demonstra como as dependências IMyService e HttpClient são injetadas em uma função disparada por 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.");
    }
}

Este exemplo usa o pacote Microsoft.Extensions.Http necessário para registrar um HttpClient na inicialização.

Tempos de vida do serviço

Os aplicativos do Azure Functions oferecem os mesmos tempos de vida de serviço que a injeção de dependência do ASP.NET. Para um aplicativo do Functions, tempos de vida de serviço diferentes se comportam da seguinte maneira:

  • Transitório: Os serviços transitórios são criados após cada resolução do serviço.
  • Com escopo: O tempo de vida do serviço com escopo corresponde a um tempo de vida de execução de função. Os serviços com escopo são criados uma vez por execução de função. Solicitações posteriores para esse serviço durante a execução reutilizam a instância de serviço existente.
  • Singleton: O tempo de vida do serviço singleton corresponde ao tempo de vida do host e é reutilizado em execuções de função nessa instância. Os serviços de vida útil singleton são recomendados para conexões e clientes, por exemplo DocumentClient ou instâncias HttpClient.

Veja ou baixe um exemplo de tempos de vida de serviço diferentes no GitHub.

Serviços de registro em log

Se você precisar de seu próprio provedor de registro em log, registre um tipo personalizado como uma instância de ILoggerProvider, que está disponível por meio do pacote NuGet Microsoft.Extensions.Logging.Abstractions.

O Application Insights é adicionado automaticamente pelo Azure Functions.

Aviso

  • Não adicione AddApplicationInsightsTelemetry() à coleção de serviços, o qual registra os serviços que entram em conflito com os serviços fornecidos pelo ambiente.
  • Não registre seu próprio TelemetryConfiguration ou TelemetryClient se você estiver usando a funcionalidade interna do Application Insights. Se você precisar configurar sua própria instância TelemetryClient, crie uma por meio de TelemetryConfiguration injetado, como mostrado em Registrar telemetria personalizada em funções C#.

ILogger<T> e ILoggerFactory

O host injeta os serviços ILogger<T> e ILoggerFactory nos construtores. No entanto, por padrão, esses novos filtros de log são filtrados dos logs de função. Você precisa modificar o arquivo host.json para aceitar filtros e categorias extras.

O exemplo a seguir demonstra como adicionar um ILogger<HttpTrigger> com logs expostos ao host.

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.");

        // ...
}

O arquivo host.json de exemplo a seguir adiciona o filtro de log.

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

Para obter mais informações sobre os níveis de log, consulte Configurar níveis de log.

Serviços oferecidos pelo aplicativo de funções

O host de função registra vários serviços. Os serviços a seguir podem ser dependências no seu aplicativo com segurança:

Tipo de Serviço Tempo de vida Descrição
Microsoft.Extensions.Configuration.IConfiguration Singleton Configuração de runtime
Microsoft.Azure.WebJobs.Host.Executors.IHostIdProvider Singleton Responsável por fornecer a ID da instância do host

Se houver outros serviços em que você queira usar uma dependência, crie um problema e faça uma proposta no GitHub.

Substituição dos serviços de host

No momento, não há serviços suportados de substituição pelo host. Se houver serviços que você queira substituir, crie um problema e faça uma proposta no GitHub.

Trabalhando com opções e configurações

Os valores definidos nas configurações do aplicativo estão disponíveis em uma instância IConfiguration, que permite que você leia os valores das configurações do aplicativo na classe de inicialização.

Você pode extrair valores da instância IConfiguration para um tipo personalizado. Copiar os valores das configurações do aplicativo para um tipo personalizado facilita o teste dos seus serviços, tornando esses valores injetáveis. As configurações lidas na instância de configuração devem ser pares simples de chave/valor. Para funções em execução em um plano Elastic Premium, os nomes de configuração do aplicativo só podem conter letras, números (0-9), períodos (.), dois-pontos (:) e sublinhados (_). Para obter mais informações, consulte as considerações de configuração do aplicativo.

Considere a seguinte classe que inclui uma propriedade nomeada de acordo com uma configuração de aplicativo:

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

E um arquivo local.settings.json que possa estruturar a configuração personalizada da seguinte maneira:

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

De dentro do método Startup.Configure, você pode extrair valores da instância IConfiguration para seu tipo personalizado usando o seguinte código:

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

Chamar Bind copia os valores da configuração, que têm nomes de propriedade correspondentes, na instância personalizada. A instância das opções agora está disponível no contêiner IoC para ser injetada em uma função.

O objeto das opções é injetado na função como uma instância da interface IOptions genérica. Use a propriedade Value para acessar os valores encontrados na sua configuração.

using System;
using Microsoft.Extensions.Options;

public class HttpTrigger
{
    private readonly MyOptions _settings;

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

Para obter mais informações, consulte o padrão Opções no ASP.NET Core.

Usando segredos de usuário do ASP.NET Core

Quando você desenvolve seu aplicativo localmente, o ASP.NET Core fornece uma ferramenta Secret Manager que permite armazenar informações secretas fora da raiz do projeto. Isso torna menos provável que os segredos sejam acidentalmente confirmados no controle do código-fonte. A Azure Functions Core Tools (versão 3.0.3233 ou posterior) lê automaticamente os segredos criados pelo Gerenciador de Segredos do ASP.NET Core.

Para configurar um projeto.NET Azure Functions para usar segredos do usuário, execute o comando a seguir na raiz do projeto.

dotnet user-secrets init

Em seguida, use o comando dotnet user-secrets set para criar ou atualizar segredos.

dotnet user-secrets set MySecret "my secret value"

Para acessar valores de segredos do usuário no código do aplicativo de funções, use IConfiguration ou IOptions.

Personalizando fontes de configuração

Para especificar outras fontes de configuração, substitua o método ConfigureAppConfiguration na classe StartUp do aplicativo de funções.

O exemplo a seguir adiciona valores de configuração de arquivos de configurações de aplicativo específicos ao ambiente base e opcionais.

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)
    {
    }
}

Adicione provedores de configuração à propriedade ConfigurationBuilder de IFunctionsConfigurationBuilder. Para obter mais informações sobre o uso de provedores de configuração, consulte Configuração no ASP.NET Core.

Um FunctionsHostBuilderContext é obtido de IFunctionsConfigurationBuilder.GetContext(). Use este contexto para recuperar o nome do ambiente atual e resolver o local dos arquivos de configuração na pasta de aplicativo de funções.

Por padrão, arquivos de configuração como appsettings.json não são copiados automaticamente para a pasta de saída do aplicativo de funções. Atualize o arquivo .csproj para corresponder ao exemplo a seguir para garantir que os arquivos sejam copiados.

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

Próximas etapas

Para saber mais, consulte os recursos a seguir: