Guida per l'esecuzione di Funzioni di Azure C# in un processo isolato

Questo articolo offre un'introduzione all'uso di C# per sviluppare funzioni di processo isolato .NET, che eseguono out-of-process in Funzioni di Azure. L'esaurimento del processo consente di separare il codice della funzione dal runtime di Funzioni di Azure. Le funzioni C# del processo isolato vengono eseguite sia in .NET 5.0 che in .NET 6.0. Le funzioni della libreria di classi C# in-process non sono supportate in .NET 5.0.

Introduzione Concetti Esempi

Perché il processo isolato .NET?

In precedenza Funzioni di Azure supportava solo una modalità strettamente integrata per le funzioni .NET, che vengono eseguite come libreria di classi nello stesso processo dell'host. Questa modalità offre un'integrazione completa tra il processo host e le funzioni. Ad esempio, le funzioni della libreria di classi .NET possono condividere API e tipi di associazione. Tuttavia, questa integrazione richiede anche un accoppiamento più stretto tra il processo host e la funzione .NET. Ad esempio, le funzioni .NET in esecuzione in-process sono necessarie per l'esecuzione nella stessa versione di .NET del runtime di Funzioni. Per consentire l'esecuzione all'esterno di questi vincoli, è ora possibile scegliere di eseguire in un processo isolato. Questo isolamento del processo consente anche di sviluppare funzioni che usano le versioni correnti di .NET (ad esempio .NET 5.0), non supportate in modo nativo dal runtime di Funzioni. Sia il processo isolato che le funzioni della libreria di classi C# in-process vengono eseguite in .NET 6.0. Per altre informazioni, vedere Versioni supportate.

Poiché queste funzioni vengono eseguite in un processo separato, esistono alcune differenze di funzionalità e funzionalità tra le app per le funzioni isolate .NET e le app per le funzioni della libreria di classi .NET.

Vantaggi dell'esaurimento del processo

Quando si esaurisce il processo, le funzioni .NET possono sfruttare i vantaggi seguenti:

  • Meno conflitti: poiché le funzioni vengono eseguite in un processo separato, gli assembly usati nell'app non saranno in conflitto con una versione diversa degli stessi assembly usati dal processo host.
  • Controllo completo del processo: è possibile controllare l'avvio dell'app e controllare le configurazioni usate e il middleware avviato.
  • Inserimento delle dipendenze: poiché si ha il controllo completo del processo, è possibile usare i comportamenti .NET correnti per l'inserimento delle dipendenze e incorporare il middleware nell'app per le funzioni.

Versioni supportate

Le versioni del runtime di Funzioni funzionano con versioni specifiche di .NET. Per altre informazioni sulle versioni di Funzioni, vedere panoramica delle versioni di runtime di Funzioni di Azure. Il supporto della versione dipende dal fatto che le funzioni vengano eseguite in-process o out-of-process (isolate).

Nota

Per informazioni su come modificare la versione del runtime di Funzioni usata dall'app per le funzioni, vedere Visualizzare e aggiornare la versione di runtime corrente.

La tabella seguente illustra il livello più alto di .NET Core o .NET Framework che può essere usato con una versione specifica di Funzioni.

Versione del runtime di Funzioni In-Process
(libreria di classi.NET)
Out-of-process
(Isolato.NET)
Funzioni 4.x .NET 6.0 .NET 6.0
Funzioni 3.x .NET Core 3.1 .NET 5.01
Funzioni 2.x .NET Core 2.12 n/d
Funzioni 1.x .NET Framework 4.8 n/d

1 Il processo di compilazione richiede anche .NET Core 3.1 SDK.
2 Per informazioni dettagliate, vedere Considerazioni sulle funzioni v2.x.

Per le ultime notizie sulle versioni di Funzioni di Azure, inclusa la rimozione di versioni secondarie precedenti specifiche, monitorare Servizio app di Azure annunci.

Progetto isolato .NET

Un progetto di funzione isolata .NET è fondamentalmente un progetto di app console .NET destinato a un runtime .NET supportato. Di seguito sono riportati i file di base necessari in qualsiasi progetto isolato .NET:

  • File host.json .
  • file local.settings.json .
  • File di progetto C# (con estensione csproj) che definisce il progetto e le dipendenze.
  • File Program.cs che rappresenta il punto di ingresso per l'app.

Nota

Per poter pubblicare il progetto di funzione isolata in un Windows o in un'app per le funzioni Linux in Azure, è necessario impostare un valore di dotnet-isolated nell'impostazione dell'applicazione FUNCTIONS_WORKER_RUNTIME remota. Per supportare la distribuzione zip e l'esecuzione dal pacchetto di distribuzione in Linux, è anche necessario aggiornare l'impostazione di configurazione del linuxFxVersion sito su DOTNET-ISOLATED|6.0. Per altre informazioni, vedere Aggiornamenti manuali delle versioni in Linux.

Riferimenti ai pacchetti

Quando si esaurisce il processo, il progetto .NET usa un set univoco di pacchetti, che implementa sia le funzionalità di base che le estensioni di associazione.

Pacchetti di base

Per eseguire le funzioni .NET in un processo isolato sono necessari i pacchetti seguenti:

Pacchetti di estensione

Poiché le funzioni eseguite in un processo isolato .NET usano tipi di associazione diversi, richiedono un set univoco di pacchetti di estensione di associazione.

Questi pacchetti di estensione sono disponibili in Microsoft.Azure.Functions.Worker.Extensions.

Avvio e configurazione

Quando si usano funzioni isolate .NET, è possibile accedere all'avvio dell'app per le funzioni, in genere in Program.cs. L'utente è responsabile della creazione e dell'avvio di un'istanza host personalizzata. Di conseguenza, è anche possibile accedere direttamente alla pipeline di configurazione per l'app. Quando si esaurisce il processo, è possibile aggiungere più facilmente configurazioni, inserire dipendenze ed eseguire il proprio middleware.

Il codice seguente illustra un esempio di pipeline HostBuilder :

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

Questo codice richiede using Microsoft.Extensions.DependencyInjection;.

Un hostBuilder viene usato per compilare e restituire un'istanza IHost completamente inizializzata, che viene eseguita in modo asincrono per avviare l'app per le funzioni.

await host.RunAsync();

Configurazione

Il metodo ConfigureFunctionsWorkerDefaults viene usato per aggiungere le impostazioni necessarie per l'esecuzione out-of-process dell'app per le funzioni, che include le funzionalità seguenti:

  • Set predefinito di convertitori.
  • Impostare l'impostazione predefinita JsonSerializerOptions per ignorare l'uso di maiuscole e minuscole sui nomi delle proprietà.
  • Eseguire l'integrazione con la registrazione di Funzioni di Azure.
  • Middleware e funzionalità di associazione di output.
  • Middleware di esecuzione delle funzioni.
  • Supporto gRPC predefinito.
.ConfigureFunctionsWorkerDefaults()

Avere accesso alla pipeline del generatore host significa che è anche possibile impostare eventuali configurazioni specifiche dell'app durante l'inizializzazione. È possibile chiamare il metodo ConfigureAppConfiguration in HostBuilder una o più volte per aggiungere le configurazioni richieste dall'app per le funzioni. Per altre informazioni sulla configurazione dell'app, vedere Configurazione in ASP.NET Core.

Queste configurazioni si applicano all'app per le funzioni in esecuzione in un processo separato. Per apportare modifiche all'host o al trigger e alla configurazione dell'associazione delle funzioni, è comunque necessario usare il file host.json.

Inserimento delle dipendenze

L'inserimento delle dipendenze è semplificato, rispetto alle librerie di classi .NET. Anziché dover creare una classe di avvio per registrare i servizi, è sufficiente chiamare ConfigureServices nel generatore host e usare i metodi di estensione in IServiceCollection per inserire servizi specifici.

Nell'esempio seguente viene inserita una dipendenza del servizio Singleton:

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

Questo codice richiede using Microsoft.Extensions.DependencyInjection;. Per altre informazioni, vedere Inserimento delle dipendenze in ASP.NET Core.

Middleware

.NET isolato supporta anche la registrazione del middleware usando un modello simile a quello presente in ASP.NET. Questo modello offre la possibilità di inserire la logica nella pipeline di chiamata e prima e dopo l'esecuzione delle funzioni.

Il metodo di estensione ConfigureFunctionsWorkerDefaults include un overload che consente di registrare il middleware personalizzato, come illustrato nell'esempio seguente.

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(workerApplication =>
    {
        // Register our custom middlewares with the worker

        workerApplication.UseMiddleware<ExceptionHandlingMiddleware>();

        workerApplication.UseMiddleware<MyCustomMiddleware>();

        workerApplication.UseWhen<StampHttpHeaderMiddleware>((context) =>
        {
            // We want to use this middleware only for http trigger invocations.
            return context.FunctionDefinition.InputBindings.Values
                          .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
        });
    })
    .Build();

Per un esempio più completo dell'uso di middleware personalizzato nell'app per le funzioni, vedere l'esempio di riferimento del middleware personalizzato.

Contesto di esecuzione

.NET isolato passa un oggetto FunctionContext ai metodi di funzione. Questo oggetto consente di ottenere un'istanza di ILogger per scrivere nei log chiamando il metodo GetLogger e fornendo una categoryName stringa. Per altre informazioni, vedere Registrazione.

Associazioni

Le associazioni vengono definite usando attributi su metodi, parametri e tipi restituiti. Un metodo di funzione è un metodo con un Function attributo e un attributo trigger applicato a un parametro di input, come illustrato nell'esempio seguente:

[Function("QueueFunction")]
[QueueOutput("output-queue")]
public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem,

    FunctionContext context)

L'attributo trigger specifica il tipo di trigger e associa i dati di input a un parametro del metodo. La funzione di esempio precedente viene attivata da un messaggio di coda e il messaggio della coda viene passato al metodo nel myQueueItem parametro.

L'attributo Function indica il metodo come punto di ingresso della funzione. Il nome deve essere univoco all'interno di un progetto, iniziare con una lettera e contenere solo lettere, numeri, _e , fino -a 127 caratteri in lunghezza. I modelli di progetto spesso creano un metodo denominato Run, ma il nome del metodo può essere qualsiasi nome di metodo c# valido.

Poiché i progetti isolati .NET vengono eseguiti in un processo di lavoro separato, le associazioni non possono sfruttare le classi di associazione avanzate, ad esempio ICollector<T>, IAsyncCollector<T>e CloudBlockBlob. Non è disponibile anche il supporto diretto per i tipi ereditati dagli SDK del servizio sottostanti, ad esempio DocumentClient e BrokeredMessage. Le associazioni si basano invece su stringhe, matrici e tipi serializzabili, ad esempio oggetti di classe precedenti (POCOs).

Per i trigger HTTP, è necessario usare HttpRequestData e HttpResponseData per accedere ai dati di richiesta e risposta. Questo è dovuto al fatto che non si ha accesso agli oggetti di richiesta e risposta HTTP originali durante l'esecuzione del processo.

Per un set completo di esempi di riferimento per l'uso di trigger e associazioni durante l'esecuzione del processo, vedere l'esempio di riferimento delle estensioni di associazione.

Associazioni di input

Una funzione può avere zero o più associazioni di input che possono passare dati a una funzione. Come i trigger, le associazioni di input vengono definite applicando un attributo di associazione a un parametro di input. Quando la funzione viene eseguita, il runtime tenta di ottenere i dati specificati nell'associazione. I dati richiesti dipendono spesso dalle informazioni fornite dal trigger usando i parametri di associazione.

Associazioni di output

Per scrivere in un'associazione di output, è necessario applicare un attributo di associazione di output al metodo di funzione, che ha definito come scrivere nel servizio associato. Il valore restituito dal metodo viene scritto nell'associazione di output. Ad esempio, nell'esempio seguente viene scritto un valore stringa in una coda di messaggi denominata myqueue-output usando un'associazione di output:

[Function("QueueFunction")]
[QueueOutput("output-queue")]
public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem,

    FunctionContext context)
{
    // Use a string array to return more than one message.
    string[] messages = {
        $"Book name = {myQueueItem.Name}",
        $"Book ID = {myQueueItem.Id}"};
    var logger = context.GetLogger("QueueFunction");
    logger.LogInformation($"{messages[0]},{messages[1]}");

    // Queue Output messages
    return messages;
}

Più associazioni di output

I dati scritti in un'associazione di output sono sempre il valore restituito della funzione. Se è necessario scrivere in più associazioni di output, è necessario creare un tipo restituito personalizzato. Questo tipo restituito deve avere l'attributo di associazione di output applicato a una o più proprietà della classe. L'esempio seguente da un trigger HTTP scrive sia nella risposta HTTP che in un'associazione di output della coda:

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

La risposta da un trigger HTTP viene sempre considerata un output, quindi non è necessario un attributo valore restituito.

Trigger HTTP

I trigger HTTP convertono il messaggio di richiesta HTTP in ingresso in un oggetto HttpRequestData passato alla funzione. Questo oggetto fornisce dati dalla richiesta, tra cui Headers, Cookies, Identities, URLe facoltativo un messaggio Body. Questo oggetto è una rappresentazione dell'oggetto richiesta HTTP e non della richiesta stessa.

Analogamente, la funzione restituisce un oggetto HttpResponseData, che fornisce dati usati per creare la risposta HTTP, incluso il messaggio StatusCodeHeaders, e facoltativamente un messaggio Body.

Il codice seguente è un trigger 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;
}

Registrazione

In .NET isolato è possibile scrivere nei log usando un'istanza di ILogger ottenuta da un oggetto FunctionContext passato alla funzione. Chiamare il metodo GetLogger , passando un valore stringa che corrisponde al nome della categoria in cui vengono scritti i log. La categoria è in genere il nome della funzione specifica da cui vengono scritti i log. Per altre informazioni sulle categorie, vedere l'articolo di monitoraggio.

Nell'esempio seguente viene illustrato come ottenere un ILogger e scrivere log all'interno di una funzione:

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

Usare vari metodi di ILogger per scrivere vari livelli di log, ad esempio LogWarning o LogError. Per altre informazioni sui livelli di log, vedere l'articolo di monitoraggio.

Viene inoltre fornito un ILogger quando si usa l'inserimento delle dipendenze.

Differenze con le funzioni della libreria di classi .NET

Questa sezione descrive lo stato corrente delle differenze funzionali e comportamentali in esecuzione su processi non elaborati rispetto alle funzioni della libreria di classi .NET in esecuzione in-process:

Funzionalità/comportamento In-Process Out-of-process
Versioni di .NET .NET Core 3.1
.NET 6.0
.NET 5.0
.NET 6.0
Pacchetti principali Microsoft.NET.Sdk.Functions Microsoft.Azure.Functions.Worker
Microsoft.Azure.Functions.Worker.Sdk
Pacchetti di estensione di associazione Microsoft.Azure.WebJobs.Extensions.* In Microsoft.Azure.Functions.Worker.Extensions.*
Registrazione ILogger passato alla funzione ILogger ottenuto da FunctionContext
Token di annullamento Supportato Non supportato
Associazioni di output Parametri out Valori restituiti
Tipi di associazioni di output IAsyncCollector, DocumentClient, BrokeredMessage e altri tipi specifici del client Tipi semplici, tipi serializzabili JSON e matrici.
Più associazioni di output Supportato Supportato
Trigger HTTP HttpRequest/ObjectResult HttpRequestData/HttpResponseData
Funzioni permanenti Supportato Non supportato
Associazioni imperative Supportato Non supportato
elemento function.json Generato Non generato
Configurazione host.json host.json e inizializzazione personalizzata
Inserimento delle dipendenze Supportato Supportato
Middleware Non supportato Supportato
Orario di inizio freddo Typical (Tipica) Più lungo, a causa dell'avvio just-in-time. Eseguire in Linux anziché Windows per ridurre potenziali ritardi.
ReadyToRun Supportato TBD

Passaggi successivi