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:
Pacote Microsoft.NET.Sdk.Functions versão 1.0.28 ou posterior
Microsoft.Extensions.DependencyInjection (atualmente, somente a versão 2.x ou posterior é compatível)
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, comoBindingContext
eExecutionContext
, 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, chamarbuilder.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ânciasHttpClient
.
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
ouTelemetryClient
se você estiver usando a funcionalidade interna do Application Insights. Se você precisar configurar sua própria instânciaTelemetryClient
, crie uma por meio deTelemetryConfiguration
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: