Guia para executar funções no .NET 5.0 no Azure

Este artigo é uma introdução ao uso do C# para desenvolver funções de processo isolado do .NET, que são executadas fora do processo no Azure Functions. A execução fora do processo permite desacoplar seu código de função do tempo de execução do Azure Functions. Ele também fornece uma maneira de criar e executar funções direcionadas para a versão atual do .NET 5.0.

Introdução Conceitos Exemplos

Se você não precisar dar suporte ao .NET 5.0 ou executar suas funções fora do processo, convém desenvolver funções de biblioteca de classes C#.

Por que o processo isolado do .NET?

Anteriormente o Azure Functions dava suporte apenas para um modo rigidamente integrado para funções do .NET, que são executadas como uma biblioteca de classes no mesmo processo que o host. Esse modo fornece uma integração profunda entre o processo de host e as funções. Por exemplo, as funções de biblioteca de classes do .NET podem compartilhar APIs e tipos de associação. No entanto, essa integração também requer um acoplamento mais rígido entre o processo de host e a função .NET. Por exemplo, as funções do .NET em execução no processo são necessárias para executar na mesma versão do .NET que o tempo de execução do Functions. Para permitir que você execute fora dessas restrições, agora você pode optar por executar em um processo isolado. Esse isolamento de processo também permite que você desenvolva funções que usam versões atuais do .NET (como o .NET 5.0), sem suporte nativo pelo tempo de execução do Functions.

Como essas funções são executadas em um processo separado, há algumas diferenças de funcionalidade e recurso entre aplicativos de funções isoladas do .NET e aplicativos de função da biblioteca de classes do .NET.

Benefícios da execução fora do processo

Ao executar fora do processo, suas funções do .NET podem aproveitar os seguintes benefícios:

  • Menos conflitos: como as funções são executadas em um processo separado, os assemblies usados em seu aplicativo não entrarão em conflito com versões diferentes dos mesmos assemblies usados pelo processo de host.
  • Controle total do processo: você controla a inicialização do aplicativo e pode controlar as configurações usadas e o middleware iniciado.
  • Injeção de dependência: como você tem controle total do processo, você pode usar os comportamentos atuais do .NET para injeção de dependência e incorporar middleware em seu aplicativo de funções.

Versões com suporte

As versões runtime do Functions funcionam com versões específicas do .NET. Saiba mais sobre as versões do Functions, confira Visão geral de versões do Azure Functions runtime. O suporte à versão depende de suas funções executarem no processo ou fora do processo (isoladas).

A tabela a seguir mostra o nível mais alto do .NET Core ou .NET Framework que pode ser usado com uma versão específica do Functions.

Versão do runtime do Functions Em processo
(Biblioteca de classes do .NET)
Fora do processo
(Isolado do .NET)
Functions 4.x (versão prévia) .NET 6.0 (versão prévia) .NET 6.0 (versão prévia)2
Functions 3.x .NET Core 3.1 .NET 5.0
Funções 2.x .NET Core 2.11 n/d
Funções 1.x .NET Framework 4.8 n/d

1 Para saber mais, confira Considerações sobre o Functions v2.x.
2 Atualmente, você pode criar apenas funções de processo isoladas usando o Azure Functions Core Tools. Para saber mais, confira Início Rápido: criar uma função C# no Azure na linha de comando.

Para receber as notícias mais recentes sobre as versões de Azure Functions, incluindo a remoção de versões secundárias específicas mais antigas, acompanhe os comunicados de Serviço de Aplicativo do Azure.

Projeto isolado do .NET

Um projeto de função isolado do .NET é basicamente um projeto de aplicativo de console .NET que tem como alvo o .NET 5.0. Estes são os arquivos básicos necessários em qualquer projeto isolado do .NET:

  • Arquivo host.json.
  • Arquivo local.settings.json.
  • Arquivo de projeto C# (.csproj) que define o projeto e as dependências.
  • Arquivo Program. cs que é o ponto de entrada para o aplicativo.

Referências de pacote

Ao executar fora do processo, seu projeto do .NET usa um conjunto exclusivo de pacotes, que implementam a funcionalidade principal e as extensões de associação.

Pacotes principais

Os pacotes a seguir são necessários para executar as funções do .NET em um processo isolado:

Pacotes de extensão

Como as funções que são executadas em um processo isolado do .NET usam tipos de associação diferentes, elas exigem um conjunto exclusivo de pacotes de extensão de associação.

Você encontrará esses pacotes de extensão em Microsoft.Azure.Functions.Worker.Extensions.

Inicialização e configuração

Ao usar funções isoladas do .NET, você tem acesso à inicialização do seu aplicativo de funções, que geralmente está em Program.cs. Você é responsável por criar e iniciar sua própria instância de host. Assim, você também tem acesso direto ao pipeline de configuração para seu aplicativo. Ao executar fora do processo, você pode adicionar configurações com mais facilidade, injetar dependências e executar seu próprio middleware.

O código a seguir mostra um exemplo de um pipeline HostBuilder:

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(s =>
    {
        s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
    })
    .Build();

Este código requer using Microsoft.Extensions.DependencyInjection;.

Um HostBuilder é usado para criar e retornar uma instância de IHost totalmente inicializada, que você executa de forma assíncrona para iniciar seu aplicativo de funções.

await host.RunAsync();

Configuração

O método ConfigureFunctionsWorkerDefaults é usado para adicionar as configurações necessárias para que o aplicativo de funções seja executado fora do processo, o que inclui a seguinte funcionalidade:

  • Conjunto padrão de conversores.
  • Defina o JsonSerializerOptions padrão para ignorar maiúsculas e minúsculas nos nomes de propriedade.
  • Integrar com o log de Azure Functions.
  • Middleware de associação de saída e recursos.
  • Middleware de execução de função.
  • Suporte a gRPC padrão.
.ConfigureFunctionsWorkerDefaults()

Ter acesso ao pipeline do host Builder significa que você também pode definir qualquer configuração específica do aplicativo durante a inicialização. Você pode chamar o método ConfigureAppConfiguration em HostBuilder uma ou mais vezes para adicionar as configurações exigidas por seu aplicativo de funções. Para obter mais informações sobre a configuração de aplicativos, consulte Configuração no ASP.NET Core.

Essas configurações se aplicam ao seu aplicativo de funções em execução em um processo separado. Para fazer alterações no host do Functions ou no gatilho e na configuração de associação, você ainda precisará usar o arquivo host.json.

Injeção de dependência

A injeção de dependência é simplificada, em comparação com as bibliotecas de classes do .NET. Em vez de precisar criar uma classe de inicialização para registrar serviços, basta chamar ConfigureServiceservices no construtor de hosts e usar os métodos de extensão em IServiceCollection para injetar serviços específicos.

O exemplo a seguir injeta uma dependência de serviço singleton:

.ConfigureServices(s =>
{
    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
})

Este código requer using Microsoft.Extensions.DependencyInjection;. Para saber mais, confira Injeção de dependência em ASP.NET Core.

Middleware

O .NET isolado também dá suporte ao registro de middleware, novamente usando um modelo semelhante ao que existe em ASP.NET. Esse modelo oferece a capacidade de injetar a lógica no pipeline de invocação e as funções before e after são executadas.

O método de extensão ConfigureFunctionsWorkerDefaults tem uma sobrecarga que permite registrar seu próprio middleware, como você pode ver no exemplo a seguir.

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(workerApplication =>
    {
        // Register our custom middleware with the worker
        workerApplication.UseMiddleware<MyCustomMiddleware>();
    })
    .Build();

Para obter um exemplo mais completo de como usar o middleware personalizado em seu aplicativo de funções, consulte o exemplo de referência de middleware personalizado.

Contexto de execução

O .NET isolado passa um objeto FunctionContext para seus métodos de função. Esse objeto permite que você obtenha uma instância de ILogger para gravar nos logs chamando o método GetLogger e fornecendo uma categoryName cadeia de caracteres. Para saber mais, consulte Logging.

Associações

As associações são definidas usando atributos em métodos, parâmetros e tipos de retorno. Um método de função é um método com um atributo Function e um atributo de gatilho aplicado a um parâmetro de entrada, conforme mostrado no exemplo a seguir:

[Function("QueueFunction")]
[QueueOutput("myqueue-output")]
public static string Run([QueueTrigger("myqueue-items")] Book myQueueItem,
    FunctionContext context)

O atributo de gatilho especifica o tipo de gatilho e associa dados de entrada a um parâmetro de método. A função de exemplo é disparada por uma mensagem de fila, a qual é transmitida para o método no parâmetro myQueueItem.

O atributo Function marca o método como um ponto de entrada da função. O nome deve ser exclusivo dentro de um projeto, começar com uma letra e conter apenas letras, números, _ e -, até 127 caracteres. Modelos de projeto geralmente criam um método chamado Run, mas o nome do método pode ser qualquer nome de método C# válido.

Como os projetos isolados do .NET são executados em um processo de trabalho separado, as associações não podem tirar proveito das classes de associação avançadas, como, ICollector<T> IAsyncCollector<T> e CloudBlockBlob. Também não há suporte direto para tipos herdados de SDKs de serviço subjacentes, como DocumentClient e BrokeredMessage. Em vez disso, as associações dependem de cadeias de caracteres, matrizes e tipos serializáveis, como POCOs (objetos de classe antiga).

Para gatilhos HTTP, você deve usar HttpRequestData e HttpResponseData para acessar os dados de solicitação e resposta. Isso ocorre porque você não tem acesso aos objetos de solicitação e resposta HTTP originais ao executar fora do processo.

Para obter um conjunto completo de exemplos de referência para usar gatilhos e associações ao executar fora do processo, consulte o exemplo de referência de extensões de associação.

Associações de entrada

Uma função pode ter zero ou mais associações de entrada que podem passar dados para uma função. Como gatilhos, as associações de entrada são definidas pela aplicação de um atributo de associação a um parâmetro de entrada. Quando a função é executada, o tempo de execução tenta obter os dados especificados na associação. Os dados que estão sendo solicitados geralmente dependem das informações fornecidas pelo gatilho usando parâmetros de associação.

Associações de saída

Para gravar em uma associação de saída, você deve aplicar um atributo de associação de saída ao método de função, que definiu como gravar no serviço associado. O valor retornado pelo método é gravado na associação de saída. Por exemplo, o exemplo a seguir grava um valor de cadeia de caracteres em uma fila de mensagens chamada myqueue-output usando uma associação de saída:

[Function("QueueFunction")]
[QueueOutput("myqueue-output")]
public static string Run([QueueTrigger("myqueue-items")] Book myQueueItem,
    FunctionContext context)
{
    var logger = context.GetLogger("QueueFunction");
    logger.LogInformation($"Book name = {myQueueItem.Name}");

    // Queue Output
    return "queue message";
}

Várias associações de saída

Os dados gravados em uma associação de saída são sempre o valor de retorno da função. Se você precisar gravar em mais de uma associação de saída, deverá criar um tipo de retorno personalizado. Esse tipo de retorno deve ter o atributo de associação de saída aplicado a uma ou mais propriedades da classe. O exemplo a seguir de um gatilho HTTP é gravado na resposta HTTP e em uma associação de saída de fila:

public static class MultiOutput
{
    [Function("MultiOutput")]
    public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
        FunctionContext context)
    {
        var response = req.CreateResponse(HttpStatusCode.OK);
        response.WriteString("Success!");

        string myQueueOutput = "some output";

        return new MyOutputType()
        {
            Name = myQueueOutput,
            HttpResponse = response
        };
    }
}

public class MyOutputType
{
    [QueueOutput("myQueue")]
    public string Name { get; set; }

    public HttpResponseData HttpResponse { get; set; }
}

A resposta de um gatilho HTTP é sempre considerada uma saída, portanto, um atributo de valor de retorno não é necessário.

Gatilho HTTP

Os gatilhos HTTP convertem a mensagem de solicitação HTTP de entrada em um objeto HttpRequestData que é passado para a função. Esse objeto fornece dados da solicitação, incluindo Headers, Cookies, Identities, URL e uma mensagem opcional Body. Esse objeto é uma representação do objeto de solicitação HTTP e não da solicitação em si.

Da mesma forma, a função retorna um objeto HttpResponseData, que fornece dados usados para criar a resposta HTTP, incluindo a mensagem StatusCode, Headers e, opcionalmente, uma mensagem Body.

O código a seguir é um gatilho HTTP

[Function("HttpFunction")]
public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
    FunctionContext executionContext)
{
    var logger = executionContext.GetLogger("HttpFunction");
    logger.LogInformation("message logged");

    var response = req.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("Date", "Mon, 18 Jul 2016 16:06:00 GMT");
    response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
    
    response.WriteString("Welcome to .NET 5!!");

    return response;
}

Log

No .NET isolado, você pode gravar em logs usando uma instância ILogger obtida de um objeto FunctionContext passado para sua função. Chame o método GetLogger, passando um valor de cadeia de caracteres que é o nome da categoria na qual os logs são gravados. A categoria geralmente é o nome da função específica da qual os logs são gravados. Para saber mais sobre categorias, consulte o artigo monitoramento.

O exemplo a seguir mostra como obter um ILogger e gravar logs dentro de uma função:

var logger = executionContext.GetLogger("HttpFunction");
logger.LogInformation("message logged");

Use vários métodos de ILogger para gravar vários níveis de log, como LogWarning ou LogError. Para saber mais sobre os níveis de log, consulte o artigo monitoramento.

Um ILogger também é fornecido ao usar a injeção de dependência.

Diferenças com funções de biblioteca de classes do .NET

Esta seção descreve o estado atual das diferenças funcionais e comportamentais em execução no .NET 5.0 fora do processo em comparação com as funções da biblioteca de classes do .NET em execução no processo:

Recurso/comportamento Em processo (.NET Core 3.1) Fora do Processo (.NET 5.0)
Versões do .NET LTS (.NET Core 3.1) Atual (.NET 5.0)
Pacotes principais Microsoft.NET.Sdk.Functions Microsoft.Azure.Functions.Worker
Microsoft.Azure.Functions.Worker.Sdk
Pacotes de extensão de associação Microsoft.Azure.WebJobs.Extensions.* Em Microsoft.Azure.Functions.Worker.Extensions.*
Log ILogger passado para a função ILogger obtido de FunctionContext
Tokens de cancelamento Com suporte Sem suporte
Associações de saída Parâmetros externos Valores retornados
Tipos de associação de saída IAsyncCollector, DocumentClient, BrokeredMessagee outros tipos específicos do cliente Tipos simples, tipos serializáveis JSON e matrizes.
Várias associações de saída Com suporte Com suporte
Gatilho HTTP HttpRequest/ObjectResult HttpRequestData/HttpResponseData
Funções duráveis Com suporte Sem suporte
Associações imperativas Com suporte Sem suporte
artefato function.json Gerado Não gerado
Configuração host. JSON host.json e inicialização personalizada
Injeção de dependência Com suporte Com suporte
Middleware Sem suporte Com suporte
Horários de inicialização a frio Típico Mais tempo, devido à inicialização just-in-time. Execute no Linux em vez do Windows para reduzir possíveis atrasos.
ReadyToRun Com suporte TBD

Problemas conhecidos

Para obter informações sobre soluções alternativas para saber problemas na execução de funções de processo isolado do .NET, consulte esta página de problemas conhecidos. Para relatar problemas, crie um problema neste repositório GitHub.

Próximas etapas