Compartilhar via


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

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

  • A injeção de dependência no Azure Functions baseia-se nos recursos de Injeção de Dependência do .NET Core. É recomendável ter familiaridade com 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 o Azure Functions 2.x.

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

Importante

As diretrizes neste artigo se aplicam apenas à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 personalizada não se aplica a funções isoladas do .NET, o que permite executar funções do .NET fora do processo. O modelo de processo de trabalho isolado do .NET depende de padrões regulares de injeção de dependência do ASP.NET Core. Para saber mais, confira a injeção de dependência no guia de processo de trabalho isolado do .NET.

Pré-requisitos

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

Registrar serviços

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

Aviso

Para aplicativos de funções em execução nos planos Consumo ou Premium, as modificações nos valores de configuração usados nos gatilhos podem causar erros de dimensionamento. Qualquer alteração nessas propriedades pela FunctionsStartup classe 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 FunctionsStartup atributo assembly que especifica o nome do 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 são executadas antes e depois que o runtime processa a classe de inicialização. Portanto, tenha em mente os seguintes itens:

  • A classe de inicialização destina-se apenas à configuraçã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 logger que está sendo registrado durante a inicialização. Esse ponto do processo de registro é muito cedo para que seus serviços estejam disponíveis para uso. Depois que o Configure método é executado, o runtime do Functions continua a registrar outras dependências, o que pode afetar a forma como seus serviços operam.

  • O contêiner de injeção de dependência contém apenas tipos registrados explicitamente. Os únicos serviços disponíveis como tipos injetáveis são os configurados no Configure método. 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 ASP.NET autenticação. O host do Functions configura os serviços de autenticação do ASP.NET para expor adequadamente as APIs para as operações principais do ciclo de vida. Outras configurações em uma classe personalizada Startup podem substituir essa configuração, causando consequências não intencionais. Por exemplo, a chamada 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 exige que você não use classes estáticas para serviços injetados ou para as 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.

Tempo 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, os diferentes tempos de vida do serviço se comportam da seguinte maneira:

  • Transitório: os serviços transitórios são criados em 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 entre 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 instâncias DocumentClient ou HttpClient.

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

Como registrar serviços em log

Se você precisar de um provedor de log próprio, 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, que registra 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 instância do TelemetryClient, crie uma por meio da TelemetryConfiguration injetada, conforme mostrado em Registrar uma telemetria personalizada em log nas 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 excluídos dos logs de funções. Você precisa modificar o host.json arquivo 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 de exemplo host.json 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 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 são seguros para serem usados como uma dependência em seu aplicativo:

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 nos quais você deseja usar uma dependência, crie um problema e proponha-os no GitHub.

Como substituir os serviços de host

Atualmente, não há suporte para sobrescrever serviços fornecidos pelo host. Se houver serviços que você deseja substituir, crie um problema e proponha-os no GitHub.

Trabalhando com opções e configurações

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

Você pode extrair valores da instância IConfiguration em um tipo personalizado. Copiar os valores de configurações do aplicativo para um tipo personalizado facilita o teste de seus serviços tornando esses valores injetáveis. As configurações lidas na instância de configuração devem ser pares chave/valor simples. 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 consistente com uma configuração de aplicativo:

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

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

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

Dentro do método Startup.Configure, você pode extrair valores da instância IConfiguration para o 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 de opções agora está disponível no contêiner de IoC para injetar em uma função.

O objeto options é injetado na função como uma instância da interface genérica IOptions . Use a Value propriedade para acessar os valores encontrados em 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 os segredos do usuário do ASP.NET Core

Quando você desenvolve seu aplicativo localmente, o ASP.NET Core fornece uma ferramenta do Gerenciador de Segredos 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. O 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 do .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 dotnet user-secrets set comando 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 como usar provedores de configuração, consulte Configuração no ASP.NET Core.

Um FunctionsHostBuilderContext é obtido de IFunctionsConfigurationBuilder.GetContext(). Use esse contexto para recuperar o nome do ambiente atual e resolver o local dos arquivos de configuração na pasta do 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 .csproj arquivo 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 obter mais informações, consulte os seguintes recursos: